@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,252 @@
1
+ import type { Context } from '../../core/context';
2
+ import type { Middleware } from '../middleware';
3
+
4
+ /**
5
+ * 速率限制存储接口
6
+ */
7
+ export interface RateLimitStore {
8
+ /**
9
+ * 获取当前计数
10
+ * @param key - 存储键
11
+ * @returns 当前计数
12
+ */
13
+ get(key: string): Promise<number>;
14
+
15
+ /**
16
+ * 增加计数
17
+ * @param key - 存储键
18
+ * @param windowMs - 时间窗口(毫秒)
19
+ * @returns 增加后的计数
20
+ */
21
+ increment(key: string, windowMs: number): Promise<number>;
22
+
23
+ /**
24
+ * 重置计数
25
+ * @param key - 存储键
26
+ */
27
+ reset(key: string): Promise<void>;
28
+ }
29
+
30
+ /**
31
+ * 内存存储实现(使用 Map)
32
+ */
33
+ export class MemoryRateLimitStore implements RateLimitStore {
34
+ private store: Map<string, { count: number; resetTime: number }> = new Map();
35
+
36
+ public async get(key: string): Promise<number> {
37
+ const entry = this.store.get(key);
38
+ if (!entry) {
39
+ return 0;
40
+ }
41
+
42
+ // 如果已过期,返回 0
43
+ if (Date.now() > entry.resetTime) {
44
+ this.store.delete(key);
45
+ return 0;
46
+ }
47
+
48
+ return entry.count;
49
+ }
50
+
51
+ public async increment(key: string, windowMs: number): Promise<number> {
52
+ const now = Date.now();
53
+ const entry = this.store.get(key);
54
+
55
+ if (!entry || now > entry.resetTime) {
56
+ // 创建新条目或重置过期条目
57
+ const resetTime = now + windowMs;
58
+ this.store.set(key, { count: 1, resetTime });
59
+ return 1;
60
+ }
61
+
62
+ // 增加计数
63
+ entry.count++;
64
+ return entry.count;
65
+ }
66
+
67
+ public async reset(key: string): Promise<void> {
68
+ this.store.delete(key);
69
+ }
70
+
71
+ /**
72
+ * 清理过期条目(可选,用于内存管理)
73
+ */
74
+ public cleanup(): void {
75
+ const now = Date.now();
76
+ for (const [key, entry] of this.store.entries()) {
77
+ if (now > entry.resetTime) {
78
+ this.store.delete(key);
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 速率限制选项
86
+ */
87
+ export interface RateLimitOptions {
88
+ /**
89
+ * 时间窗口内的最大请求数
90
+ */
91
+ max: number;
92
+
93
+ /**
94
+ * 时间窗口(毫秒)
95
+ * @default 60000 (1 分钟)
96
+ */
97
+ windowMs?: number;
98
+
99
+ /**
100
+ * 存储实现(默认使用内存存储)
101
+ */
102
+ store?: RateLimitStore;
103
+
104
+ /**
105
+ * 获取限流键的函数
106
+ * @param context - 请求上下文
107
+ * @returns 限流键
108
+ */
109
+ keyGenerator?: (context: Context) => string | Promise<string>;
110
+
111
+ /**
112
+ * 是否跳过成功响应(只对错误响应计数)
113
+ * @default false
114
+ */
115
+ skipSuccessfulRequests?: boolean;
116
+
117
+ /**
118
+ * 是否跳过失败响应(只对成功响应计数)
119
+ * @default false
120
+ */
121
+ skipFailedRequests?: boolean;
122
+
123
+ /**
124
+ * 自定义错误消息
125
+ */
126
+ message?: string;
127
+
128
+ /**
129
+ * 自定义错误状态码
130
+ * @default 429
131
+ */
132
+ statusCode?: number;
133
+
134
+ /**
135
+ * 是否在响应头中包含限流信息
136
+ * @default true
137
+ */
138
+ standardHeaders?: boolean;
139
+
140
+ /**
141
+ * 是否启用 X-RateLimit-* 响应头
142
+ * @default true
143
+ */
144
+ legacyHeaders?: boolean;
145
+ }
146
+
147
+ /**
148
+ * 默认键生成器:基于 IP 地址
149
+ */
150
+ function defaultKeyGenerator(context: Context): string {
151
+ return `rate-limit:${context.getClientIp()}`;
152
+ }
153
+
154
+ /**
155
+ * 创建速率限制中间件
156
+ */
157
+ export function createRateLimitMiddleware(options: RateLimitOptions): Middleware {
158
+ const {
159
+ max,
160
+ windowMs = 60000, // 默认 1 分钟
161
+ store = new MemoryRateLimitStore(),
162
+ keyGenerator = defaultKeyGenerator,
163
+ skipSuccessfulRequests = false,
164
+ skipFailedRequests = false,
165
+ message = 'Too Many Requests',
166
+ statusCode = 429,
167
+ standardHeaders = true,
168
+ legacyHeaders = true,
169
+ } = options;
170
+
171
+ return async (context: Context, next) => {
172
+ // 生成限流键
173
+ const key = await keyGenerator(context);
174
+ const currentCount = await store.increment(key, windowMs);
175
+
176
+ // 计算剩余请求数和重置时间
177
+ const remaining = Math.max(0, max - currentCount);
178
+ const resetTime = Date.now() + windowMs;
179
+
180
+ // 设置响应头
181
+ if (standardHeaders) {
182
+ context.setHeader('RateLimit-Limit', max.toString());
183
+ context.setHeader('RateLimit-Remaining', remaining.toString());
184
+ context.setHeader('RateLimit-Reset', Math.ceil(resetTime / 1000).toString());
185
+ }
186
+
187
+ if (legacyHeaders) {
188
+ context.setHeader('X-RateLimit-Limit', max.toString());
189
+ context.setHeader('X-RateLimit-Remaining', remaining.toString());
190
+ context.setHeader('X-RateLimit-Reset', Math.ceil(resetTime / 1000).toString());
191
+ }
192
+
193
+ // 检查是否超过限制
194
+ if (currentCount > max) {
195
+ context.setStatus(statusCode);
196
+ return context.createResponse({
197
+ error: message,
198
+ retryAfter: Math.ceil(windowMs / 1000),
199
+ });
200
+ }
201
+
202
+ // 执行下一个中间件或路由处理器
203
+ const response = await next();
204
+
205
+ // 根据选项决定是否计数
206
+ const shouldSkip =
207
+ (skipSuccessfulRequests && response.status >= 200 && response.status < 300) ||
208
+ (skipFailedRequests && response.status >= 400);
209
+
210
+ if (shouldSkip) {
211
+ // 如果跳过,需要减少计数(因为之前已经增加了)
212
+ const current = await store.get(key);
213
+ if (current > 0) {
214
+ // 注意:这里不能直接减少,因为滑动窗口算法不支持
215
+ // 所以这个选项在滑动窗口算法下效果有限
216
+ // 更好的做法是在请求成功后不增加计数,但这需要重构
217
+ }
218
+ }
219
+
220
+ return response;
221
+ };
222
+ }
223
+
224
+ /**
225
+ * 基于 Token/User 的键生成器
226
+ */
227
+ export function createTokenKeyGenerator(tokenHeader: string = 'Authorization'): (context: Context) => string {
228
+ return (context: Context) => {
229
+ const token = context.getHeader(tokenHeader);
230
+ if (token) {
231
+ // 提取 token(可能包含 Bearer 前缀)
232
+ const tokenValue = token.startsWith('Bearer ') ? token.substring(7) : token;
233
+ return `rate-limit:token:${tokenValue}`;
234
+ }
235
+ // 如果没有 token,回退到 IP
236
+ return defaultKeyGenerator(context);
237
+ };
238
+ }
239
+
240
+ /**
241
+ * 基于用户 ID 的键生成器(需要从认证上下文获取)
242
+ */
243
+ export function createUserKeyGenerator(getUserId: (context: Context) => string | null | undefined): (context: Context) => string {
244
+ return (context: Context) => {
245
+ const userId = getUserId(context);
246
+ if (userId) {
247
+ return `rate-limit:user:${userId}`;
248
+ }
249
+ // 如果没有用户 ID,回退到 IP
250
+ return defaultKeyGenerator(context);
251
+ };
252
+ }
@@ -0,0 +1,88 @@
1
+ import { normalize, resolve, sep } from 'path';
2
+
3
+ import type { Middleware } from '../middleware';
4
+ import type { HeadersInit } from 'bun'
5
+
6
+ export interface StaticFileOptions {
7
+ root: string;
8
+ prefix?: string;
9
+ indexFile?: string;
10
+ enableCache?: boolean;
11
+ headers?: HeadersInit | Headers;
12
+ }
13
+
14
+ function isSubPath(root: string, target: string): boolean {
15
+ const normalizedRoot = normalize(root);
16
+ const normalizedTarget = normalize(target);
17
+ if (normalizedRoot === normalizedTarget) {
18
+ return true;
19
+ }
20
+ const rootWithSep = normalizedRoot.endsWith(sep) ? normalizedRoot : normalizedRoot + sep;
21
+ return normalizedTarget.startsWith(rootWithSep);
22
+ }
23
+
24
+ /**
25
+ * 静态文件服务中间件
26
+ */
27
+ export function createStaticFileMiddleware(options: StaticFileOptions): Middleware {
28
+ if (!options.root) {
29
+ throw new Error('Static file middleware requires a root directory');
30
+ }
31
+
32
+ const root = resolve(options.root);
33
+ const prefix = options.prefix ?? '/';
34
+ const indexFile = options.indexFile ?? 'index.html';
35
+ const enableCache = options.enableCache ?? true;
36
+
37
+ return async (context, next) => {
38
+ if (!context.path.startsWith(prefix)) {
39
+ return await next();
40
+ }
41
+
42
+ let relativePath = context.path.slice(prefix.length);
43
+ if (!relativePath || relativePath === '') {
44
+ relativePath = '/';
45
+ }
46
+
47
+ let cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
48
+ if (cleanPath === '') {
49
+ cleanPath = '.';
50
+ }
51
+
52
+ const segments = cleanPath.split('/').filter((segment) => segment.length > 0);
53
+ if (segments.some((segment) => segment === '..')) {
54
+ context.setStatus(403);
55
+ return context.createResponse({ error: 'Forbidden' });
56
+ }
57
+
58
+ let targetPath = resolve(root, cleanPath);
59
+ if (context.path.endsWith('/') || relativePath === '/') {
60
+ targetPath = resolve(root, cleanPath, indexFile);
61
+ }
62
+
63
+ if (!isSubPath(root, targetPath)) {
64
+ context.setStatus(403);
65
+ return context.createResponse({ error: 'Forbidden' });
66
+ }
67
+
68
+ const file = Bun.file(targetPath);
69
+ if (!(await file.exists())) {
70
+ return await next();
71
+ }
72
+
73
+ const headers = new Headers(options.headers);
74
+ if (enableCache) {
75
+ headers.set('Cache-Control', 'public, max-age=31536000, immutable');
76
+ }
77
+ if (!headers.has('Content-Type') && file.type) {
78
+ headers.set('Content-Type', file.type);
79
+ }
80
+
81
+ return new Response(file, {
82
+ status: 200,
83
+ headers,
84
+ });
85
+ };
86
+ }
87
+
88
+
@@ -0,0 +1,91 @@
1
+ import 'reflect-metadata';
2
+ import type { Middleware } from './middleware';
3
+ import { createRateLimitMiddleware, type RateLimitOptions } from './builtin/rate-limit';
4
+
5
+ const CLASS_MIDDLEWARE_METADATA_KEY = Symbol('middleware:class');
6
+ const METHOD_MIDDLEWARE_METADATA_KEY = Symbol('middleware:method');
7
+
8
+ /**
9
+ * 注册中间件到元数据
10
+ * @param target - 目标对象
11
+ * @param propertyKey - 属性键
12
+ * @param middlewares - 中间件列表
13
+ */
14
+ function appendMiddlewareMetadata(
15
+ target: object,
16
+ propertyKey: string | symbol | undefined,
17
+ middlewares: Middleware[],
18
+ ): void {
19
+ if (!middlewares.length) {
20
+ return;
21
+ }
22
+
23
+ if (propertyKey === undefined) {
24
+ const existing = (Reflect.getMetadata(CLASS_MIDDLEWARE_METADATA_KEY, target) as Middleware[]) || [];
25
+ Reflect.defineMetadata(CLASS_MIDDLEWARE_METADATA_KEY, existing.concat(middlewares), target);
26
+ return;
27
+ }
28
+
29
+ const existing =
30
+ (Reflect.getMetadata(METHOD_MIDDLEWARE_METADATA_KEY, target, propertyKey) as Middleware[]) || [];
31
+ Reflect.defineMetadata(
32
+ METHOD_MIDDLEWARE_METADATA_KEY,
33
+ existing.concat(middlewares),
34
+ target,
35
+ propertyKey,
36
+ );
37
+ }
38
+
39
+ /**
40
+ * UseMiddleware 装饰器
41
+ * 可用于控制器类或方法
42
+ * @param middlewares - 中间件列表
43
+ */
44
+ export function UseMiddleware(...middlewares: Middleware[]): ClassDecorator & MethodDecorator {
45
+ return function (target: object, propertyKey?: string | symbol) {
46
+ appendMiddlewareMetadata(
47
+ propertyKey === undefined ? (target as object) : target,
48
+ propertyKey,
49
+ middlewares,
50
+ );
51
+ };
52
+ }
53
+
54
+ /**
55
+ * RateLimit 装饰器
56
+ * 用于在控制器方法上应用速率限制
57
+ * @param options - 速率限制选项
58
+ */
59
+ export function RateLimit(options: RateLimitOptions): MethodDecorator {
60
+ return function (target: object, propertyKey: string | symbol) {
61
+ const middleware = createRateLimitMiddleware(options);
62
+ appendMiddlewareMetadata(target, propertyKey, [middleware]);
63
+ };
64
+ }
65
+
66
+ /**
67
+ * 获取类级中间件
68
+ * @param constructor - 控制器构造函数
69
+ * @returns 中间件列表
70
+ */
71
+ export function getClassMiddlewares(constructor: new (...args: unknown[]) => unknown): Middleware[] {
72
+ return (
73
+ (Reflect.getMetadata(CLASS_MIDDLEWARE_METADATA_KEY, constructor) as Middleware[]) || []
74
+ );
75
+ }
76
+
77
+ /**
78
+ * 获取方法级中间件
79
+ * @param target - 控制器原型
80
+ * @param propertyKey - 方法名
81
+ * @returns 中间件列表
82
+ */
83
+ export function getMethodMiddlewares(
84
+ target: object,
85
+ propertyKey: string | symbol,
86
+ ): Middleware[] {
87
+ return (
88
+ (Reflect.getMetadata(METHOD_MIDDLEWARE_METADATA_KEY, target, propertyKey) as Middleware[]) ||
89
+ []
90
+ );
91
+ }
@@ -0,0 +1,11 @@
1
+ export type { Middleware, NextFunction } from './middleware';
2
+ export { MiddlewarePipeline, runMiddlewares } from './pipeline';
3
+ export { UseMiddleware, RateLimit, getClassMiddlewares, getMethodMiddlewares } from './decorators';
4
+ export {
5
+ createLoggerMiddleware,
6
+ createRequestLoggingMiddleware,
7
+ createErrorHandlingMiddleware,
8
+ createCorsMiddleware,
9
+ } from './builtin';
10
+
11
+
@@ -0,0 +1,13 @@
1
+ import type { Context } from '../core/context';
2
+
3
+ /**
4
+ * 中间件 Next 函数
5
+ */
6
+ export type NextFunction = () => Promise<Response>;
7
+
8
+ /**
9
+ * 中间件接口
10
+ */
11
+ export type Middleware = (context: Context, next: NextFunction) => Promise<Response> | Response;
12
+
13
+
@@ -0,0 +1,93 @@
1
+ import type { Context } from '../core/context';
2
+ import type { Middleware, NextFunction } from './middleware';
3
+
4
+ /**
5
+ * 中间件执行管道
6
+ */
7
+ export class MiddlewarePipeline {
8
+ private readonly middlewares: Middleware[] = [];
9
+
10
+ public constructor(initialMiddlewares: Middleware[] = []) {
11
+ this.middlewares.push(...initialMiddlewares);
12
+ }
13
+
14
+ /**
15
+ * 注册中间件
16
+ * @param middleware - 要注册的中间件
17
+ */
18
+ public use(middleware: Middleware): void {
19
+ this.middlewares.push(middleware);
20
+ }
21
+
22
+ /**
23
+ * 清空所有中间件
24
+ */
25
+ public clear(): void {
26
+ this.middlewares.length = 0;
27
+ }
28
+
29
+ /**
30
+ * 是否存在中间件
31
+ */
32
+ public hasMiddlewares(): boolean {
33
+ return this.middlewares.length > 0;
34
+ }
35
+
36
+ /**
37
+ * 执行中间件管道
38
+ * @param context - 请求上下文
39
+ * @param finalHandler - 最终处理函数
40
+ * @returns 响应对象
41
+ */
42
+ public async run(context: Context, finalHandler: NextFunction): Promise<Response> {
43
+ const length = this.middlewares.length;
44
+ if (length === 0) {
45
+ return await finalHandler();
46
+ }
47
+
48
+ // 优化:使用索引而不是数组来跟踪调用状态,减少内存分配
49
+ let currentIndex = 0;
50
+ const called = new Array<boolean>(length).fill(false);
51
+
52
+ // 创建链式调用函数(从后往前构建)
53
+ const createNext = (index: number): NextFunction => {
54
+ if (index >= length) {
55
+ return finalHandler;
56
+ }
57
+
58
+ return async () => {
59
+ if (called[index]) {
60
+ throw new Error('next() called multiple times');
61
+ }
62
+ called[index] = true;
63
+ currentIndex = index + 1;
64
+ const middleware = this.middlewares[index];
65
+ return await middleware(context, createNext(index + 1));
66
+ };
67
+ };
68
+
69
+ return await createNext(0)();
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 使用指定的中间件队列执行一次性管道
75
+ * @param middlewares - 中间件数组
76
+ * @param context - 请求上下文
77
+ * @param finalHandler - 最终处理函数
78
+ * @returns 响应对象
79
+ */
80
+ export async function runMiddlewares(
81
+ middlewares: Middleware[],
82
+ context: Context,
83
+ finalHandler: NextFunction,
84
+ ): Promise<Response> {
85
+ if (middlewares.length === 0) {
86
+ return await finalHandler();
87
+ }
88
+
89
+ const pipeline = new MiddlewarePipeline(middlewares);
90
+ return await pipeline.run(context, finalHandler);
91
+ }
92
+
93
+
@@ -0,0 +1,110 @@
1
+ import 'reflect-metadata';
2
+
3
+ /**
4
+ * 队列装饰器元数据键
5
+ */
6
+ const QUEUE_METADATA_KEY = Symbol('@dangao/bun-server:queue:queue');
7
+ const CRON_METADATA_KEY = Symbol('@dangao/bun-server:queue:cron');
8
+
9
+ /**
10
+ * 队列配置
11
+ */
12
+ export interface QueueOptions {
13
+ /**
14
+ * 队列名称
15
+ * @default 'default'
16
+ */
17
+ name?: string;
18
+ }
19
+
20
+ /**
21
+ * Cron 配置
22
+ */
23
+ export interface CronDecoratorOptions {
24
+ /**
25
+ * Cron 表达式
26
+ */
27
+ pattern: string;
28
+
29
+ /**
30
+ * 时区
31
+ * @default 'UTC'
32
+ */
33
+ timezone?: string;
34
+
35
+ /**
36
+ * 是否立即执行一次
37
+ * @default false
38
+ */
39
+ runOnInit?: boolean;
40
+
41
+ /**
42
+ * 队列名称
43
+ * @default 'default'
44
+ */
45
+ queueName?: string;
46
+ }
47
+
48
+ /**
49
+ * 队列装饰器元数据
50
+ */
51
+ export interface QueueMetadata {
52
+ name?: string;
53
+ }
54
+
55
+ export interface CronMetadata {
56
+ pattern: string;
57
+ timezone?: string;
58
+ runOnInit?: boolean;
59
+ queueName?: string;
60
+ }
61
+
62
+ /**
63
+ * 标记方法为队列任务处理器
64
+ * @param options - 队列配置
65
+ */
66
+ export function Queue(options: QueueOptions = {}): MethodDecorator {
67
+ return function (
68
+ target: unknown,
69
+ propertyKey: string | symbol,
70
+ descriptor: PropertyDescriptor,
71
+ ) {
72
+ const metadata: QueueMetadata = {
73
+ name: options.name,
74
+ };
75
+
76
+ Reflect.defineMetadata(QUEUE_METADATA_KEY, metadata, descriptor.value);
77
+ };
78
+ }
79
+
80
+ /**
81
+ * 标记方法为定时任务(Cron)
82
+ * @param options - Cron 配置
83
+ */
84
+ export function Cron(options: CronDecoratorOptions): MethodDecorator {
85
+ return function (
86
+ target: unknown,
87
+ propertyKey: string | symbol,
88
+ descriptor: PropertyDescriptor,
89
+ ) {
90
+ const metadata: CronMetadata = {
91
+ pattern: options.pattern,
92
+ timezone: options.timezone,
93
+ runOnInit: options.runOnInit ?? false,
94
+ queueName: options.queueName,
95
+ };
96
+
97
+ Reflect.defineMetadata(CRON_METADATA_KEY, metadata, descriptor.value);
98
+ };
99
+ }
100
+
101
+ /**
102
+ * 获取队列装饰器元数据
103
+ */
104
+ export function getQueueMetadata(target: unknown): QueueMetadata | undefined {
105
+ return Reflect.getMetadata(QUEUE_METADATA_KEY, target as object);
106
+ }
107
+
108
+ export function getCronMetadata(target: unknown): CronMetadata | undefined {
109
+ return Reflect.getMetadata(CRON_METADATA_KEY, target as object);
110
+ }
@@ -0,0 +1,26 @@
1
+ export { QueueModule } from './queue-module';
2
+ export { QueueService } from './service';
3
+ export {
4
+ Queue,
5
+ Cron,
6
+ getQueueMetadata,
7
+ getCronMetadata,
8
+ type QueueOptions,
9
+ type CronDecoratorOptions,
10
+ type QueueMetadata,
11
+ type CronMetadata,
12
+ } from './decorators';
13
+ export {
14
+ MemoryQueueStore,
15
+ QUEUE_SERVICE_TOKEN,
16
+ QUEUE_OPTIONS_TOKEN,
17
+ } from './types';
18
+ export type {
19
+ QueueStore,
20
+ QueueModuleOptions,
21
+ Job,
22
+ JobData,
23
+ JobHandler,
24
+ JobOptions,
25
+ CronOptions,
26
+ } from './types';