@dangao/bun-server 1.0.1 → 1.1.2
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.
- package/dist/controller/controller.d.ts +1 -1
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/database/database-extension.d.ts.map +1 -1
- package/dist/database/database-module.d.ts.map +1 -1
- package/dist/database/orm/transaction-decorator.d.ts +1 -0
- package/dist/database/orm/transaction-decorator.d.ts.map +1 -1
- package/dist/database/orm/transaction-interceptor.d.ts +12 -3
- package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +678 -310
- package/dist/interceptor/base-interceptor.d.ts +94 -0
- package/dist/interceptor/base-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/cache-interceptor.d.ts +69 -0
- package/dist/interceptor/builtin/cache-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/index.d.ts +4 -0
- package/dist/interceptor/builtin/index.d.ts.map +1 -0
- package/dist/interceptor/builtin/log-interceptor.d.ts +56 -0
- package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/permission-interceptor.d.ts +70 -0
- package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -0
- package/dist/interceptor/index.d.ts +7 -0
- package/dist/interceptor/index.d.ts.map +1 -0
- package/dist/interceptor/interceptor-chain.d.ts +22 -0
- package/dist/interceptor/interceptor-chain.d.ts.map +1 -0
- package/dist/interceptor/interceptor-registry.d.ts +59 -0
- package/dist/interceptor/interceptor-registry.d.ts.map +1 -0
- package/dist/interceptor/metadata.d.ts +12 -0
- package/dist/interceptor/metadata.d.ts.map +1 -0
- package/dist/interceptor/types.d.ts +42 -0
- package/dist/interceptor/types.d.ts.map +1 -0
- package/dist/middleware/decorators.d.ts +2 -1
- package/dist/middleware/decorators.d.ts.map +1 -1
- package/dist/router/decorators.d.ts.map +1 -1
- package/dist/router/registry.d.ts +2 -1
- package/dist/router/registry.d.ts.map +1 -1
- package/dist/router/route.d.ts +3 -2
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/router.d.ts +2 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/websocket/decorators.d.ts +2 -1
- package/dist/websocket/decorators.d.ts.map +1 -1
- package/package.json +5 -3
- package/readme.md +163 -2
- package/src/auth/controller.ts +148 -0
- package/src/auth/decorators.ts +81 -0
- package/src/auth/index.ts +12 -0
- package/src/auth/jwt.ts +169 -0
- package/src/auth/oauth2.ts +244 -0
- package/src/auth/types.ts +248 -0
- package/src/cache/cache-module.ts +67 -0
- package/src/cache/decorators.ts +202 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/service.ts +151 -0
- package/src/cache/types.ts +420 -0
- package/src/config/config-module.ts +76 -0
- package/src/config/index.ts +8 -0
- package/src/config/service.ts +93 -0
- package/src/config/types.ts +27 -0
- package/src/controller/controller.ts +278 -0
- package/src/controller/decorators.ts +84 -0
- package/src/controller/index.ts +7 -0
- package/src/controller/metadata.ts +27 -0
- package/src/controller/param-binder.ts +157 -0
- package/src/core/application.ts +239 -0
- package/src/core/context.ts +228 -0
- package/src/core/index.ts +4 -0
- package/src/core/server.ts +128 -0
- package/src/core/types.ts +2 -0
- package/src/database/connection-manager.ts +239 -0
- package/src/database/connection-pool.ts +322 -0
- package/src/database/database-extension.ts +83 -0
- package/src/database/database-module.ts +121 -0
- package/src/database/health-indicator.ts +51 -0
- package/src/database/index.ts +47 -0
- package/src/database/orm/decorators.ts +155 -0
- package/src/database/orm/drizzle-repository.ts +39 -0
- package/src/database/orm/index.ts +23 -0
- package/src/database/orm/repository-decorator.ts +39 -0
- package/src/database/orm/repository.ts +103 -0
- package/src/database/orm/service.ts +49 -0
- package/src/database/orm/transaction-decorator.ts +76 -0
- package/src/database/orm/transaction-interceptor.ts +263 -0
- package/src/database/orm/transaction-manager.ts +276 -0
- package/src/database/orm/transaction-types.ts +140 -0
- package/src/database/orm/types.ts +99 -0
- package/src/database/service.ts +221 -0
- package/src/database/types.ts +171 -0
- package/src/di/container.ts +398 -0
- package/src/di/decorators.ts +228 -0
- package/src/di/index.ts +4 -0
- package/src/di/module-registry.ts +188 -0
- package/src/di/module.ts +65 -0
- package/src/di/types.ts +67 -0
- package/src/error/error-codes.ts +222 -0
- package/src/error/filter.ts +43 -0
- package/src/error/handler.ts +66 -0
- package/src/error/http-exception.ts +115 -0
- package/src/error/i18n.ts +217 -0
- package/src/error/index.ts +16 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/logger-extension.ts +31 -0
- package/src/extensions/logger-module.ts +69 -0
- package/src/extensions/types.ts +14 -0
- package/src/files/index.ts +5 -0
- package/src/files/static-middleware.ts +53 -0
- package/src/files/storage.ts +67 -0
- package/src/files/types.ts +33 -0
- package/src/files/upload-middleware.ts +45 -0
- package/src/health/controller.ts +76 -0
- package/src/health/health-module.ts +51 -0
- package/src/health/index.ts +12 -0
- package/src/health/types.ts +28 -0
- package/src/index.ts +292 -0
- package/src/interceptor/base-interceptor.ts +203 -0
- package/src/interceptor/builtin/cache-interceptor.ts +169 -0
- package/src/interceptor/builtin/index.ts +28 -0
- package/src/interceptor/builtin/log-interceptor.ts +178 -0
- package/src/interceptor/builtin/permission-interceptor.ts +173 -0
- package/src/interceptor/index.ts +26 -0
- package/src/interceptor/interceptor-chain.ts +79 -0
- package/src/interceptor/interceptor-registry.ts +132 -0
- package/src/interceptor/metadata.ts +40 -0
- package/src/interceptor/types.ts +52 -0
- package/src/metrics/collector.ts +209 -0
- package/src/metrics/controller.ts +40 -0
- package/src/metrics/index.ts +15 -0
- package/src/metrics/metrics-module.ts +58 -0
- package/src/metrics/middleware.ts +46 -0
- package/src/metrics/prometheus.ts +79 -0
- package/src/metrics/types.ts +103 -0
- package/src/middleware/builtin/cors.ts +60 -0
- package/src/middleware/builtin/error-handler.ts +90 -0
- package/src/middleware/builtin/file-upload.ts +42 -0
- package/src/middleware/builtin/index.ts +14 -0
- package/src/middleware/builtin/logger.ts +91 -0
- package/src/middleware/builtin/rate-limit.ts +252 -0
- package/src/middleware/builtin/static-file.ts +88 -0
- package/src/middleware/decorators.ts +92 -0
- package/src/middleware/index.ts +11 -0
- package/src/middleware/middleware.ts +13 -0
- package/src/middleware/pipeline.ts +93 -0
- package/src/queue/decorators.ts +110 -0
- package/src/queue/index.ts +26 -0
- package/src/queue/queue-module.ts +64 -0
- package/src/queue/service.ts +302 -0
- package/src/queue/types.ts +341 -0
- package/src/request/body-parser.ts +133 -0
- package/src/request/file-handler.ts +46 -0
- package/src/request/index.ts +5 -0
- package/src/request/request.ts +107 -0
- package/src/request/response.ts +150 -0
- package/src/router/decorators.ts +123 -0
- package/src/router/index.ts +6 -0
- package/src/router/registry.ts +99 -0
- package/src/router/route.ts +141 -0
- package/src/router/router.ts +242 -0
- package/src/router/types.ts +27 -0
- package/src/security/access-decision-manager.ts +34 -0
- package/src/security/authentication-manager.ts +47 -0
- package/src/security/context.ts +92 -0
- package/src/security/filter.ts +162 -0
- package/src/security/index.ts +8 -0
- package/src/security/providers/index.ts +3 -0
- package/src/security/providers/jwt-provider.ts +60 -0
- package/src/security/providers/oauth2-provider.ts +70 -0
- package/src/security/security-module.ts +145 -0
- package/src/security/types.ts +165 -0
- package/src/session/decorators.ts +45 -0
- package/src/session/index.ts +19 -0
- package/src/session/middleware.ts +143 -0
- package/src/session/service.ts +218 -0
- package/src/session/session-module.ts +69 -0
- package/src/session/types.ts +373 -0
- package/src/swagger/decorators.ts +133 -0
- package/src/swagger/generator.ts +234 -0
- package/src/swagger/index.ts +7 -0
- package/src/swagger/swagger-extension.ts +41 -0
- package/src/swagger/swagger-module.ts +83 -0
- package/src/swagger/types.ts +188 -0
- package/src/swagger/ui.ts +98 -0
- package/src/testing/harness.ts +96 -0
- package/src/validation/decorators.ts +95 -0
- package/src/validation/errors.ts +28 -0
- package/src/validation/index.ts +14 -0
- package/src/validation/types.ts +35 -0
- package/src/validation/validator.ts +63 -0
- package/src/websocket/decorators.ts +53 -0
- package/src/websocket/index.ts +12 -0
- package/src/websocket/registry.ts +133 -0
- package/tests/cache/cache-module.test.ts +212 -0
- package/tests/config/config-module.test.ts +151 -0
- package/tests/controller/controller.test.ts +189 -0
- package/tests/controller/path-combination.test.ts +207 -0
- package/tests/core/application.test.ts +57 -0
- package/tests/core/context-body.test.ts +44 -0
- package/tests/core/context.test.ts +86 -0
- package/tests/core/edge-cases.test.ts +432 -0
- package/tests/database/database-module.test.ts +385 -0
- package/tests/database/orm.test.ts +164 -0
- package/tests/database/postgres-mysql-integration.test.ts +395 -0
- package/tests/database/transaction.test.ts +238 -0
- package/tests/di/container.test.ts +264 -0
- package/tests/di/module.test.ts +128 -0
- package/tests/error/error-codes.test.ts +121 -0
- package/tests/error/error-handler.test.ts +68 -0
- package/tests/error/error-handling.test.ts +254 -0
- package/tests/error/http-exception.test.ts +37 -0
- package/tests/error/i18n-integration.test.ts +175 -0
- package/tests/extensions/logger-extension.test.ts +40 -0
- package/tests/files/static-middleware.test.ts +67 -0
- package/tests/files/upload-middleware.test.ts +43 -0
- package/tests/health/health-module.test.ts +116 -0
- package/tests/integration/application-router.test.ts +85 -0
- package/tests/integration/body-parsing.test.ts +88 -0
- package/tests/integration/cache-e2e.test.ts +114 -0
- package/tests/integration/oauth2-e2e.test.ts +615 -0
- package/tests/integration/session-e2e.test.ts +207 -0
- package/tests/interceptor/builtin/cache-interceptor.test.ts +137 -0
- package/tests/interceptor/builtin/permission-interceptor.test.ts +182 -0
- package/tests/interceptor/interceptor-advanced-integration.test.ts +592 -0
- package/tests/interceptor/interceptor-arg-modification.test.ts +76 -0
- package/tests/interceptor/interceptor-chain.test.ts +199 -0
- package/tests/interceptor/interceptor-integration.test.ts +230 -0
- package/tests/interceptor/interceptor-registry.test.ts +200 -0
- package/tests/interceptor/perf/interceptor-performance.test.ts +341 -0
- package/tests/metrics/metrics-module.test.ts +178 -0
- package/tests/middleware/builtin.test.ts +206 -0
- package/tests/middleware/file-upload.test.ts +41 -0
- package/tests/middleware/middleware.test.ts +120 -0
- package/tests/middleware/pipeline.test.ts +72 -0
- package/tests/middleware/rate-limit.test.ts +314 -0
- package/tests/middleware/static-file.test.ts +62 -0
- package/tests/perf/harness.test.ts +48 -0
- package/tests/perf/optimization.test.ts +183 -0
- package/tests/perf/regression.test.ts +120 -0
- package/tests/queue/queue-module.test.ts +217 -0
- package/tests/request/body-parser.test.ts +96 -0
- package/tests/request/response.test.ts +99 -0
- package/tests/router/decorators.test.ts +46 -0
- package/tests/router/registry.test.ts +51 -0
- package/tests/router/route.test.ts +71 -0
- package/tests/router/router-normalization.test.ts +106 -0
- package/tests/router/router.test.ts +133 -0
- package/tests/security/access-decision-manager.test.ts +84 -0
- package/tests/security/authentication-manager.test.ts +81 -0
- package/tests/security/context.test.ts +302 -0
- package/tests/security/filter.test.ts +225 -0
- package/tests/security/jwt-provider.test.ts +106 -0
- package/tests/security/oauth2-provider.test.ts +269 -0
- package/tests/security/security-module.test.ts +143 -0
- package/tests/session/session-module.test.ts +307 -0
- package/tests/stress/di-stress.test.ts +30 -0
- package/tests/swagger/decorators.test.ts +153 -0
- package/tests/swagger/generator.test.ts +202 -0
- package/tests/swagger/swagger-extension.test.ts +72 -0
- package/tests/swagger/swagger-module.test.ts +79 -0
- package/tests/utils/test-port.ts +10 -0
- package/tests/validation/controller-validation.test.ts +64 -0
- package/tests/validation/validation.test.ts +42 -0
- package/tests/websocket/gateway.test.ts +68 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求体解析器
|
|
3
|
+
* 支持 JSON、FormData、URLEncoded 等格式
|
|
4
|
+
*/
|
|
5
|
+
export class BodyParser {
|
|
6
|
+
/**
|
|
7
|
+
* 解析请求体
|
|
8
|
+
* @param request - 请求对象
|
|
9
|
+
* @returns 解析后的请求体
|
|
10
|
+
*/
|
|
11
|
+
public static async parse(request: Request): Promise<unknown> {
|
|
12
|
+
const contentType = request.headers.get('content-type') ?? '';
|
|
13
|
+
|
|
14
|
+
// 检查是否有请求体(GET 和 HEAD 请求通常没有 body)
|
|
15
|
+
if (request.method === 'GET' || request.method === 'HEAD') {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 检查 Content-Length
|
|
20
|
+
const contentLength = request.headers.get('content-length');
|
|
21
|
+
if (contentLength === '0') {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 尝试解析请求体
|
|
26
|
+
try {
|
|
27
|
+
// JSON 格式
|
|
28
|
+
if (contentType.includes('application/json')) {
|
|
29
|
+
const result = await this.parseJSON(request);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// FormData 格式
|
|
34
|
+
if (contentType.includes('multipart/form-data')) {
|
|
35
|
+
return await this.parseFormData(request);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// URLEncoded 格式
|
|
39
|
+
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
40
|
+
const result = await this.parseURLEncoded(request);
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 文本格式
|
|
45
|
+
if (contentType.includes('text/')) {
|
|
46
|
+
return await this.parseText(request);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 如果没有 Content-Type 或 Content-Length,尝试读取 body
|
|
50
|
+
// 如果读取成功,尝试解析为 JSON,否则返回文本
|
|
51
|
+
if (!contentType && !contentLength) {
|
|
52
|
+
try {
|
|
53
|
+
const text = await request.text();
|
|
54
|
+
if (!text || text.length === 0) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
// 尝试解析为 JSON
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(text);
|
|
60
|
+
} catch {
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 默认尝试 JSON,失败后返回原始文本
|
|
69
|
+
const fallbackText = await request.text();
|
|
70
|
+
if (!fallbackText) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(fallbackText);
|
|
75
|
+
} catch {
|
|
76
|
+
return fallbackText;
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// 如果读取失败,返回 undefined
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 解析 JSON 格式
|
|
86
|
+
* @param request - 请求对象
|
|
87
|
+
* @returns 解析后的对象
|
|
88
|
+
*/
|
|
89
|
+
private static async parseJSON(request: Request): Promise<unknown> {
|
|
90
|
+
const text = await request.text();
|
|
91
|
+
if (!text) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return JSON.parse(text);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 解析 FormData 格式
|
|
99
|
+
* @param request - 请求对象
|
|
100
|
+
* @returns 解析后的 FormData 对象
|
|
101
|
+
*/
|
|
102
|
+
private static async parseFormData(request: Request): Promise<FormData> {
|
|
103
|
+
return await request.formData() as FormData;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 解析 URLEncoded 格式
|
|
108
|
+
* @param request - 请求对象
|
|
109
|
+
* @returns 解析后的对象
|
|
110
|
+
*/
|
|
111
|
+
private static async parseURLEncoded(request: Request): Promise<Record<string, string> | undefined> {
|
|
112
|
+
const text = await request.text();
|
|
113
|
+
if (!text || text.length === 0) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const params = new URLSearchParams(text);
|
|
118
|
+
const result: Record<string, string> = {};
|
|
119
|
+
params.forEach((value, key) => {
|
|
120
|
+
result[key] = value;
|
|
121
|
+
});
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 解析文本格式
|
|
127
|
+
* @param request - 请求对象
|
|
128
|
+
* @returns 文本内容
|
|
129
|
+
*/
|
|
130
|
+
private static async parseText(request: Request): Promise<string> {
|
|
131
|
+
return await request.text();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Context } from '../core/context';
|
|
2
|
+
|
|
3
|
+
export interface UploadedFile {
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
size: number;
|
|
7
|
+
data: ArrayBuffer;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class FileHandler {
|
|
11
|
+
private static readonly MAX_SIZE = 10 * 1024 * 1024; // 10MB
|
|
12
|
+
|
|
13
|
+
public static async parseFormData(context: Context): Promise<FormData> {
|
|
14
|
+
const contentType = context.getHeader('Content-Type');
|
|
15
|
+
if (!contentType || !contentType.includes('multipart/form-data')) {
|
|
16
|
+
throw new Error('Content-Type must be multipart/form-data');
|
|
17
|
+
}
|
|
18
|
+
const request = context.request;
|
|
19
|
+
return await request.formData() as FormData;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static async getFiles(formData: FormData): Promise<Record<string, UploadedFile[]>> {
|
|
23
|
+
const files: Record<string, UploadedFile[]> = {};
|
|
24
|
+
for (const [key, value] of formData.entries()) {
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
if (value instanceof File) {
|
|
27
|
+
const buffer = await value.arrayBuffer();
|
|
28
|
+
if (buffer.byteLength > this.MAX_SIZE) {
|
|
29
|
+
throw new Error(`File ${value.name} exceeds maximum size`);
|
|
30
|
+
}
|
|
31
|
+
if (!files[key]) {
|
|
32
|
+
files[key] = [];
|
|
33
|
+
}
|
|
34
|
+
files[key].push({
|
|
35
|
+
name: value.name,
|
|
36
|
+
type: value.type,
|
|
37
|
+
size: value.size,
|
|
38
|
+
data: buffer,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return files;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { BodyParser } from './body-parser';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 请求封装类
|
|
5
|
+
* 扩展原生 Request,提供便捷方法
|
|
6
|
+
*/
|
|
7
|
+
export class RequestWrapper {
|
|
8
|
+
/**
|
|
9
|
+
* 原始请求对象
|
|
10
|
+
*/
|
|
11
|
+
public readonly request: Request;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 请求 URL
|
|
15
|
+
*/
|
|
16
|
+
public readonly url: URL;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 请求方法
|
|
20
|
+
*/
|
|
21
|
+
public readonly method: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 请求路径
|
|
25
|
+
*/
|
|
26
|
+
public readonly path: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 查询参数
|
|
30
|
+
*/
|
|
31
|
+
public readonly query: URLSearchParams;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 请求头
|
|
35
|
+
*/
|
|
36
|
+
public readonly headers: Headers;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 请求体(解析后的)
|
|
40
|
+
*/
|
|
41
|
+
private _body?: unknown;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 是否已解析请求体
|
|
45
|
+
*/
|
|
46
|
+
private _bodyParsed: boolean = false;
|
|
47
|
+
|
|
48
|
+
public constructor(request: Request) {
|
|
49
|
+
this.request = request;
|
|
50
|
+
this.url = new URL(request.url);
|
|
51
|
+
this.method = request.method;
|
|
52
|
+
this.path = this.url.pathname;
|
|
53
|
+
this.query = this.url.searchParams;
|
|
54
|
+
this.headers = request.headers;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 获取请求体(自动解析)
|
|
59
|
+
* @returns 解析后的请求体
|
|
60
|
+
*/
|
|
61
|
+
public async body(): Promise<unknown> {
|
|
62
|
+
if (!this._bodyParsed) {
|
|
63
|
+
this._body = await BodyParser.parse(this.request);
|
|
64
|
+
this._bodyParsed = true;
|
|
65
|
+
}
|
|
66
|
+
return this._body;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 获取请求体(已解析的,如果未解析则返回 undefined)
|
|
71
|
+
* @returns 请求体或 undefined
|
|
72
|
+
*/
|
|
73
|
+
public getBody(): unknown {
|
|
74
|
+
return this._body;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 获取查询参数
|
|
79
|
+
* @param key - 参数名
|
|
80
|
+
* @returns 参数值
|
|
81
|
+
*/
|
|
82
|
+
public getQuery(key: string): string | null {
|
|
83
|
+
return this.query.get(key);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 获取所有查询参数
|
|
88
|
+
* @returns 查询参数对象
|
|
89
|
+
*/
|
|
90
|
+
public getQueryAll(): Record<string, string> {
|
|
91
|
+
const result: Record<string, string> = {};
|
|
92
|
+
this.query.forEach((value, key) => {
|
|
93
|
+
result[key] = value;
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 获取请求头
|
|
100
|
+
* @param key - 头名称
|
|
101
|
+
* @returns 头值
|
|
102
|
+
*/
|
|
103
|
+
public getHeader(key: string): string | null {
|
|
104
|
+
return this.headers.get(key);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { BodyInit, HeadersInit } from 'bun';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 响应封装类
|
|
5
|
+
* 提供便捷的响应创建方法
|
|
6
|
+
*/
|
|
7
|
+
export class ResponseBuilder {
|
|
8
|
+
/**
|
|
9
|
+
* 创建 JSON 响应
|
|
10
|
+
* @param data - 响应数据
|
|
11
|
+
* @param status - HTTP 状态码
|
|
12
|
+
* @param headers - 响应头
|
|
13
|
+
* @returns Response 对象
|
|
14
|
+
*/
|
|
15
|
+
public static json(
|
|
16
|
+
data: unknown,
|
|
17
|
+
status: number = 200,
|
|
18
|
+
headers?: HeadersInit,
|
|
19
|
+
): Response {
|
|
20
|
+
const responseHeaders = new Headers(headers);
|
|
21
|
+
responseHeaders.set('Content-Type', 'application/json');
|
|
22
|
+
|
|
23
|
+
return new Response(JSON.stringify(data), {
|
|
24
|
+
status,
|
|
25
|
+
headers: responseHeaders,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 创建文本响应
|
|
31
|
+
* @param text - 响应文本
|
|
32
|
+
* @param status - HTTP 状态码
|
|
33
|
+
* @param headers - 响应头
|
|
34
|
+
* @returns Response 对象
|
|
35
|
+
*/
|
|
36
|
+
public static text(
|
|
37
|
+
text: string,
|
|
38
|
+
status: number = 200,
|
|
39
|
+
headers?: HeadersInit,
|
|
40
|
+
): Response {
|
|
41
|
+
const responseHeaders = new Headers(headers);
|
|
42
|
+
responseHeaders.set('Content-Type', 'text/plain');
|
|
43
|
+
|
|
44
|
+
return new Response(text, {
|
|
45
|
+
status,
|
|
46
|
+
headers: responseHeaders,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 创建 HTML 响应
|
|
52
|
+
* @param html - HTML 内容
|
|
53
|
+
* @param status - HTTP 状态码
|
|
54
|
+
* @param headers - 响应头
|
|
55
|
+
* @returns Response 对象
|
|
56
|
+
*/
|
|
57
|
+
public static html(
|
|
58
|
+
html: string,
|
|
59
|
+
status: number = 200,
|
|
60
|
+
headers?: HeadersInit,
|
|
61
|
+
): Response {
|
|
62
|
+
const responseHeaders = new Headers(headers);
|
|
63
|
+
responseHeaders.set('Content-Type', 'text/html');
|
|
64
|
+
|
|
65
|
+
return new Response(html, {
|
|
66
|
+
status,
|
|
67
|
+
headers: responseHeaders,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 创建空响应
|
|
73
|
+
* @param status - HTTP 状态码
|
|
74
|
+
* @param headers - 响应头
|
|
75
|
+
* @returns Response 对象
|
|
76
|
+
*/
|
|
77
|
+
public static empty(status: number = 204, headers?: HeadersInit): Response {
|
|
78
|
+
const responseHeaders = headers ? new Headers(headers) : undefined;
|
|
79
|
+
return new Response(null, {
|
|
80
|
+
status,
|
|
81
|
+
headers: responseHeaders,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 创建重定向响应
|
|
87
|
+
* @param url - 重定向 URL
|
|
88
|
+
* @param status - HTTP 状态码(默认 302)
|
|
89
|
+
* @returns Response 对象
|
|
90
|
+
*/
|
|
91
|
+
public static redirect(url: string, status: number = 302): Response {
|
|
92
|
+
return new Response(null, {
|
|
93
|
+
status,
|
|
94
|
+
headers: {
|
|
95
|
+
Location: url,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 创建错误响应
|
|
102
|
+
* @param message - 错误消息
|
|
103
|
+
* @param status - HTTP 状态码
|
|
104
|
+
* @returns Response 对象
|
|
105
|
+
*/
|
|
106
|
+
public static error(message: string, status: number = 500): Response {
|
|
107
|
+
return this.json({ error: message }, status);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 创建文件/下载响应
|
|
112
|
+
* @param source - 文件路径或二进制数据
|
|
113
|
+
* @param options - 响应配置
|
|
114
|
+
* @returns Response 对象
|
|
115
|
+
*/
|
|
116
|
+
public static file(
|
|
117
|
+
source: string | Blob | ArrayBuffer | ArrayBufferView,
|
|
118
|
+
options: {
|
|
119
|
+
fileName?: string;
|
|
120
|
+
contentType?: string;
|
|
121
|
+
status?: number;
|
|
122
|
+
headers?: HeadersInit;
|
|
123
|
+
} = {},
|
|
124
|
+
): Response {
|
|
125
|
+
const headers = new Headers(options.headers);
|
|
126
|
+
if (options.fileName) {
|
|
127
|
+
headers.set('Content-Disposition', `attachment; filename="${options.fileName}"`);
|
|
128
|
+
}
|
|
129
|
+
if (options.contentType) {
|
|
130
|
+
headers.set('Content-Type', options.contentType);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (typeof source === 'string') {
|
|
134
|
+
const file = Bun.file(source);
|
|
135
|
+
if (!headers.has('Content-Type') && file.type) {
|
|
136
|
+
headers.set('Content-Type', file.type);
|
|
137
|
+
}
|
|
138
|
+
return new Response(file, {
|
|
139
|
+
status: options.status ?? 200,
|
|
140
|
+
headers,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return new Response(source as BodyInit, {
|
|
145
|
+
status: options.status ?? 200,
|
|
146
|
+
headers,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { RouteHandler } from './types';
|
|
3
|
+
import { RouteRegistry } from './registry';
|
|
4
|
+
import type { HttpMethod } from './types';
|
|
5
|
+
import { CONTROLLER_METADATA_KEY } from '../controller/controller';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 路由元数据键
|
|
9
|
+
*/
|
|
10
|
+
export const ROUTE_METADATA_KEY = Symbol('route');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 路由元数据
|
|
14
|
+
*/
|
|
15
|
+
export interface RouteMetadata {
|
|
16
|
+
method: HttpMethod;
|
|
17
|
+
path: string;
|
|
18
|
+
handler: RouteHandler;
|
|
19
|
+
/**
|
|
20
|
+
* 属性键(用于控制器方法)
|
|
21
|
+
*/
|
|
22
|
+
propertyKey?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 路由装饰器工厂
|
|
27
|
+
* @param method - HTTP 方法
|
|
28
|
+
* @param path - 路由路径
|
|
29
|
+
* @returns 方法装饰器
|
|
30
|
+
*/
|
|
31
|
+
function createRouteDecorator(method: HttpMethod, path: string) {
|
|
32
|
+
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
33
|
+
// 注意:装饰器应用顺序问题
|
|
34
|
+
// 方法装饰器(@GET)在类装饰器(@Controller)之前应用
|
|
35
|
+
// 因此这里无法立即检查 @Controller,需要在 ControllerRegistry.register 时验证
|
|
36
|
+
|
|
37
|
+
// 尝试检查类是否已经有 @Controller 装饰器(如果装饰器已经应用)
|
|
38
|
+
// 这可以捕获一些早期错误,但不是 100% 可靠(因为装饰器应用顺序)
|
|
39
|
+
const constructor = target.constructor;
|
|
40
|
+
if (constructor && typeof constructor === 'function') {
|
|
41
|
+
const hasController = Reflect.getMetadata(CONTROLLER_METADATA_KEY, constructor);
|
|
42
|
+
if (hasController === undefined) {
|
|
43
|
+
// 类装饰器可能还没有应用,这是正常的
|
|
44
|
+
// 如果类装饰器已经应用但没有 @Controller,这里会捕获到
|
|
45
|
+
// 但这种情况很少见,因为通常 @Controller 会在 @GET 之前应用
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 保存元数据
|
|
50
|
+
// 注意:即使类没有 @Controller,元数据也会被保存
|
|
51
|
+
// 但 ControllerRegistry.register 会验证并抛出错误,所以不会造成问题
|
|
52
|
+
const existingRoutes: RouteMetadata[] = Reflect.getMetadata(ROUTE_METADATA_KEY, target) || [];
|
|
53
|
+
|
|
54
|
+
// 获取方法名:优先使用 propertyKey,如果不可用则从函数名获取
|
|
55
|
+
let propertyKeyStr: string;
|
|
56
|
+
if (propertyKey && propertyKey !== '') {
|
|
57
|
+
propertyKeyStr = typeof propertyKey === 'string' ? propertyKey : String(propertyKey);
|
|
58
|
+
} else {
|
|
59
|
+
// 从函数名获取(可能不可靠,但作为后备方案)
|
|
60
|
+
propertyKeyStr = descriptor.value.name || '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 如果仍然没有方法名,尝试从原型中查找
|
|
64
|
+
if (!propertyKeyStr) {
|
|
65
|
+
const propertyNames = Object.getOwnPropertyNames(target);
|
|
66
|
+
for (const key of propertyNames) {
|
|
67
|
+
const targetDescriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
68
|
+
if (targetDescriptor?.value === descriptor.value) {
|
|
69
|
+
propertyKeyStr = key;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
existingRoutes.push({
|
|
76
|
+
method,
|
|
77
|
+
path,
|
|
78
|
+
handler: descriptor.value as RouteHandler,
|
|
79
|
+
propertyKey: propertyKeyStr || undefined,
|
|
80
|
+
});
|
|
81
|
+
Reflect.defineMetadata(ROUTE_METADATA_KEY, existingRoutes, target);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* GET 路由装饰器
|
|
87
|
+
* @param path - 路由路径
|
|
88
|
+
*/
|
|
89
|
+
export function GET(path: string) {
|
|
90
|
+
return createRouteDecorator('GET', path);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* POST 路由装饰器
|
|
95
|
+
* @param path - 路由路径
|
|
96
|
+
*/
|
|
97
|
+
export function POST(path: string) {
|
|
98
|
+
return createRouteDecorator('POST', path);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* PUT 路由装饰器
|
|
103
|
+
* @param path - 路由路径
|
|
104
|
+
*/
|
|
105
|
+
export function PUT(path: string) {
|
|
106
|
+
return createRouteDecorator('PUT', path);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* DELETE 路由装饰器
|
|
111
|
+
* @param path - 路由路径
|
|
112
|
+
*/
|
|
113
|
+
export function DELETE(path: string) {
|
|
114
|
+
return createRouteDecorator('DELETE', path);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* PATCH 路由装饰器
|
|
119
|
+
* @param path - 路由路径
|
|
120
|
+
*/
|
|
121
|
+
export function PATCH(path: string) {
|
|
122
|
+
return createRouteDecorator('PATCH', path);
|
|
123
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Router } from './router';
|
|
2
|
+
import type { Constructor } from '../core/types';
|
|
3
|
+
import type { HttpMethod, RouteHandler } from './types';
|
|
4
|
+
import type { Middleware } from '../middleware';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 路由注册表
|
|
8
|
+
* 全局路由注册表,管理所有路由
|
|
9
|
+
*/
|
|
10
|
+
export class RouteRegistry {
|
|
11
|
+
private static instance: RouteRegistry;
|
|
12
|
+
private readonly router: Router;
|
|
13
|
+
|
|
14
|
+
private constructor() {
|
|
15
|
+
this.router = new Router();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 获取单例实例
|
|
20
|
+
* @returns 路由注册表实例
|
|
21
|
+
*/
|
|
22
|
+
public static getInstance(): RouteRegistry {
|
|
23
|
+
if (!RouteRegistry.instance) {
|
|
24
|
+
RouteRegistry.instance = new RouteRegistry();
|
|
25
|
+
}
|
|
26
|
+
return RouteRegistry.instance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 注册路由
|
|
31
|
+
* @param method - HTTP 方法
|
|
32
|
+
* @param path - 路由路径
|
|
33
|
+
* @param handler - 路由处理器
|
|
34
|
+
* @param middlewares - 中间件列表
|
|
35
|
+
* @param controllerClass - 控制器类(可选)
|
|
36
|
+
* @param methodName - 方法名(可选)
|
|
37
|
+
*/
|
|
38
|
+
public register(
|
|
39
|
+
method: HttpMethod,
|
|
40
|
+
path: string,
|
|
41
|
+
handler: RouteHandler,
|
|
42
|
+
middlewares: Middleware[] = [],
|
|
43
|
+
controllerClass?: Constructor<unknown>,
|
|
44
|
+
methodName?: string,
|
|
45
|
+
): void {
|
|
46
|
+
this.router.register(method, path, handler, middlewares, controllerClass, methodName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 注册 GET 路由
|
|
51
|
+
*/
|
|
52
|
+
public get(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
53
|
+
this.router.get(path, handler, middlewares);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 注册 POST 路由
|
|
58
|
+
*/
|
|
59
|
+
public post(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
60
|
+
this.router.post(path, handler, middlewares);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 注册 PUT 路由
|
|
65
|
+
*/
|
|
66
|
+
public put(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
67
|
+
this.router.put(path, handler, middlewares);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 注册 DELETE 路由
|
|
72
|
+
*/
|
|
73
|
+
public delete(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
74
|
+
this.router.delete(path, handler, middlewares);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 注册 PATCH 路由
|
|
79
|
+
*/
|
|
80
|
+
public patch(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
81
|
+
this.router.patch(path, handler, middlewares);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 获取路由器实例
|
|
86
|
+
* @returns 路由器实例
|
|
87
|
+
*/
|
|
88
|
+
public getRouter(): Router {
|
|
89
|
+
return this.router;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 清除所有路由(主要用于测试)
|
|
94
|
+
*/
|
|
95
|
+
public clear(): void {
|
|
96
|
+
this.router.clear();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|