@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,592 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
|
|
3
|
+
import { Application } from '../../src/core/application';
|
|
4
|
+
import { Controller, ControllerRegistry } from '../../src/controller/controller';
|
|
5
|
+
import { GET, POST } from '../../src/router/decorators';
|
|
6
|
+
import { Param } from '../../src/controller/decorators';
|
|
7
|
+
import { RouteRegistry } from '../../src/router/registry';
|
|
8
|
+
import {
|
|
9
|
+
InterceptorRegistry,
|
|
10
|
+
INTERCEPTOR_REGISTRY_TOKEN,
|
|
11
|
+
BaseInterceptor,
|
|
12
|
+
} from '../../src/interceptor';
|
|
13
|
+
import type { Interceptor } from '../../src/interceptor';
|
|
14
|
+
import type { Container } from '../../src/di/container';
|
|
15
|
+
import type { Context } from '../../src/core/context';
|
|
16
|
+
import type { Middleware } from '../../src/middleware';
|
|
17
|
+
import { UseMiddleware } from '../../src/middleware';
|
|
18
|
+
import { getTestPort } from '../utils/test-port';
|
|
19
|
+
|
|
20
|
+
describe('Interceptor Advanced Integration', () => {
|
|
21
|
+
let app: Application;
|
|
22
|
+
let port: number;
|
|
23
|
+
let interceptorRegistry: InterceptorRegistry;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
port = getTestPort();
|
|
27
|
+
app = new Application({ port });
|
|
28
|
+
interceptorRegistry = app.getContainer().resolve<InterceptorRegistry>(
|
|
29
|
+
INTERCEPTOR_REGISTRY_TOKEN,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
if (app) {
|
|
35
|
+
await app.stop();
|
|
36
|
+
}
|
|
37
|
+
RouteRegistry.getInstance().clear();
|
|
38
|
+
ControllerRegistry.getInstance().clear();
|
|
39
|
+
interceptorRegistry.clear();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('Multiple Interceptors Combination', () => {
|
|
43
|
+
test('should execute 5 interceptors in correct priority order', async () => {
|
|
44
|
+
const METADATA_KEYS = [
|
|
45
|
+
Symbol('test:1'),
|
|
46
|
+
Symbol('test:2'),
|
|
47
|
+
Symbol('test:3'),
|
|
48
|
+
Symbol('test:4'),
|
|
49
|
+
Symbol('test:5'),
|
|
50
|
+
];
|
|
51
|
+
const executionOrder: number[] = [];
|
|
52
|
+
|
|
53
|
+
// 创建装饰器
|
|
54
|
+
function createDecorator(key: symbol, order: number): MethodDecorator {
|
|
55
|
+
return (target, propertyKey) => {
|
|
56
|
+
Reflect.defineMetadata(key, order, target, propertyKey);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 创建拦截器
|
|
61
|
+
const interceptors: Interceptor[] = METADATA_KEYS.map((key, index) => ({
|
|
62
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
63
|
+
executionOrder.push(index + 1);
|
|
64
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
// 注册拦截器(优先级:5, 4, 3, 2, 1)
|
|
69
|
+
interceptors.forEach((interceptor, index) => {
|
|
70
|
+
interceptorRegistry.register(METADATA_KEYS[index], interceptor, (index + 1) * 20);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
@Controller('/api/test')
|
|
74
|
+
class TestController {
|
|
75
|
+
@GET('/')
|
|
76
|
+
@createDecorator(METADATA_KEYS[0], 1)
|
|
77
|
+
@createDecorator(METADATA_KEYS[1], 2)
|
|
78
|
+
@createDecorator(METADATA_KEYS[2], 3)
|
|
79
|
+
@createDecorator(METADATA_KEYS[3], 4)
|
|
80
|
+
@createDecorator(METADATA_KEYS[4], 5)
|
|
81
|
+
public test() {
|
|
82
|
+
executionOrder.push(6);
|
|
83
|
+
return { message: 'test' };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
app.registerController(TestController);
|
|
88
|
+
await app.listen();
|
|
89
|
+
|
|
90
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
91
|
+
expect(response.status).toBe(200);
|
|
92
|
+
const data = await response.json();
|
|
93
|
+
expect(data.message).toBe('test');
|
|
94
|
+
// 执行顺序:1 (20) -> 2 (40) -> 3 (60) -> 4 (80) -> 5 (100) -> method
|
|
95
|
+
expect(executionOrder).toEqual([1, 2, 3, 4, 5, 6]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should handle interceptors modifying return value', async () => {
|
|
99
|
+
const METADATA_KEY = Symbol('test:modify-result');
|
|
100
|
+
|
|
101
|
+
function CustomDecorator(): MethodDecorator {
|
|
102
|
+
return (target, propertyKey) => {
|
|
103
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const interceptor: Interceptor = {
|
|
108
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
109
|
+
// 执行原方法
|
|
110
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
111
|
+
// 修改返回值:将 result 字段乘以 2
|
|
112
|
+
if (result && typeof result === 'object' && 'result' in result) {
|
|
113
|
+
return { ...result, result: (result as any).result * 2 };
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
120
|
+
|
|
121
|
+
@Controller('/api/test')
|
|
122
|
+
class TestController {
|
|
123
|
+
@GET('/:value')
|
|
124
|
+
@CustomDecorator()
|
|
125
|
+
public test(@Param('value') value: string) {
|
|
126
|
+
const num = Number.parseInt(value);
|
|
127
|
+
return { original: num, result: num };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
app.registerController(TestController);
|
|
132
|
+
await app.listen();
|
|
133
|
+
|
|
134
|
+
const response = await fetch(`http://localhost:${port}/api/test/5`);
|
|
135
|
+
expect(response.status).toBe(200);
|
|
136
|
+
const data = await response.json();
|
|
137
|
+
// 拦截器将 result 从 5 改为 10
|
|
138
|
+
expect(data.original).toBe(5);
|
|
139
|
+
expect(data.result).toBe(10);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('Interceptor and Middleware Interaction', () => {
|
|
144
|
+
test('should execute middleware before interceptors', async () => {
|
|
145
|
+
const METADATA_KEY = Symbol('test:middleware-order');
|
|
146
|
+
const executionOrder: string[] = [];
|
|
147
|
+
|
|
148
|
+
function CustomDecorator(): MethodDecorator {
|
|
149
|
+
return (target, propertyKey) => {
|
|
150
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 创建中间件
|
|
155
|
+
const middleware: Middleware = async (context, next) => {
|
|
156
|
+
executionOrder.push('middleware');
|
|
157
|
+
return await next();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// 创建拦截器
|
|
161
|
+
const interceptor: Interceptor = {
|
|
162
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
163
|
+
executionOrder.push('interceptor');
|
|
164
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
169
|
+
app.use(middleware);
|
|
170
|
+
|
|
171
|
+
@Controller('/api/test')
|
|
172
|
+
class TestController {
|
|
173
|
+
@GET('/')
|
|
174
|
+
@CustomDecorator()
|
|
175
|
+
public test() {
|
|
176
|
+
executionOrder.push('method');
|
|
177
|
+
return { message: 'test' };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
app.registerController(TestController);
|
|
182
|
+
await app.listen();
|
|
183
|
+
|
|
184
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
185
|
+
expect(response.status).toBe(200);
|
|
186
|
+
// 执行顺序:middleware -> interceptor -> method
|
|
187
|
+
expect(executionOrder).toEqual(['middleware', 'interceptor', 'method']);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('should execute class-level and method-level middleware with interceptors', async () => {
|
|
191
|
+
const METADATA_KEY = Symbol('test:multi-middleware');
|
|
192
|
+
const executionOrder: string[] = [];
|
|
193
|
+
|
|
194
|
+
function CustomDecorator(): MethodDecorator {
|
|
195
|
+
return (target, propertyKey) => {
|
|
196
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 创建多个中间件
|
|
201
|
+
const globalMiddleware: Middleware = async (context, next) => {
|
|
202
|
+
executionOrder.push('global-middleware');
|
|
203
|
+
return await next();
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const classMiddleware: Middleware = async (context, next) => {
|
|
207
|
+
executionOrder.push('class-middleware');
|
|
208
|
+
return await next();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const methodMiddleware: Middleware = async (context, next) => {
|
|
212
|
+
executionOrder.push('method-middleware');
|
|
213
|
+
return await next();
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// 创建拦截器
|
|
217
|
+
const interceptor: Interceptor = {
|
|
218
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
219
|
+
executionOrder.push('interceptor');
|
|
220
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
225
|
+
app.use(globalMiddleware);
|
|
226
|
+
|
|
227
|
+
@Controller('/api/test')
|
|
228
|
+
@UseMiddleware(classMiddleware)
|
|
229
|
+
class TestController {
|
|
230
|
+
@GET('/')
|
|
231
|
+
@UseMiddleware(methodMiddleware)
|
|
232
|
+
@CustomDecorator()
|
|
233
|
+
public test() {
|
|
234
|
+
executionOrder.push('method');
|
|
235
|
+
return { message: 'test' };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
app.registerController(TestController);
|
|
240
|
+
await app.listen();
|
|
241
|
+
|
|
242
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
243
|
+
expect(response.status).toBe(200);
|
|
244
|
+
// 执行顺序:global -> class -> method -> interceptor -> method
|
|
245
|
+
expect(executionOrder).toEqual([
|
|
246
|
+
'global-middleware',
|
|
247
|
+
'class-middleware',
|
|
248
|
+
'method-middleware',
|
|
249
|
+
'interceptor',
|
|
250
|
+
'method',
|
|
251
|
+
]);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('should handle middleware modifying context before interceptors', async () => {
|
|
255
|
+
const METADATA_KEY = Symbol('test:context-modify');
|
|
256
|
+
let interceptorReceivedHeader: string | null = null;
|
|
257
|
+
|
|
258
|
+
function CustomDecorator(): MethodDecorator {
|
|
259
|
+
return (target, propertyKey) => {
|
|
260
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 创建中间件,在上下文中存储数据
|
|
265
|
+
const middleware: Middleware = async (context, next) => {
|
|
266
|
+
// 使用自定义属性存储数据(中间件设置的 header 是响应头,拦截器无法读取)
|
|
267
|
+
(context as any).customData = 'modified-by-middleware';
|
|
268
|
+
return await next();
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// 创建拦截器,读取上下文数据
|
|
272
|
+
const interceptor: Interceptor = {
|
|
273
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
274
|
+
if (context) {
|
|
275
|
+
interceptorReceivedHeader = (context as any).customData || null;
|
|
276
|
+
}
|
|
277
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
282
|
+
app.use(middleware);
|
|
283
|
+
|
|
284
|
+
@Controller('/api/test')
|
|
285
|
+
class TestController {
|
|
286
|
+
@GET('/')
|
|
287
|
+
@CustomDecorator()
|
|
288
|
+
public test() {
|
|
289
|
+
return { message: 'test' };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
app.registerController(TestController);
|
|
294
|
+
await app.listen();
|
|
295
|
+
|
|
296
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
297
|
+
expect(response.status).toBe(200);
|
|
298
|
+
expect(interceptorReceivedHeader).toBe('modified-by-middleware');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Interceptor Error Handling', () => {
|
|
303
|
+
test('should handle interceptor throwing error before method execution', async () => {
|
|
304
|
+
const METADATA_KEY = Symbol('test:error-before');
|
|
305
|
+
|
|
306
|
+
function CustomDecorator(): MethodDecorator {
|
|
307
|
+
return (target, propertyKey) => {
|
|
308
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const interceptor: Interceptor = {
|
|
313
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
314
|
+
throw new Error('Interceptor error before method');
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
319
|
+
|
|
320
|
+
@Controller('/api/test')
|
|
321
|
+
class TestController {
|
|
322
|
+
@GET('/')
|
|
323
|
+
@CustomDecorator()
|
|
324
|
+
public test() {
|
|
325
|
+
return { message: 'test' };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
app.registerController(TestController);
|
|
330
|
+
await app.listen();
|
|
331
|
+
|
|
332
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
333
|
+
expect(response.status).toBe(500);
|
|
334
|
+
const data = await response.json();
|
|
335
|
+
expect(data.error).toBeDefined();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('should handle interceptor catching and transforming method error', async () => {
|
|
339
|
+
const METADATA_KEY = Symbol('test:error-transform');
|
|
340
|
+
|
|
341
|
+
function CustomDecorator(): MethodDecorator {
|
|
342
|
+
return (target, propertyKey) => {
|
|
343
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const interceptor: Interceptor = {
|
|
348
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
349
|
+
try {
|
|
350
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
351
|
+
} catch (error) {
|
|
352
|
+
// 转换错误
|
|
353
|
+
return { error: 'Transformed error', original: (error as Error).message };
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
359
|
+
|
|
360
|
+
@Controller('/api/test')
|
|
361
|
+
class TestController {
|
|
362
|
+
@GET('/')
|
|
363
|
+
@CustomDecorator()
|
|
364
|
+
public test() {
|
|
365
|
+
throw new Error('Original error');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
app.registerController(TestController);
|
|
370
|
+
await app.listen();
|
|
371
|
+
|
|
372
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
373
|
+
expect(response.status).toBe(200);
|
|
374
|
+
const data = await response.json();
|
|
375
|
+
expect(data.error).toBe('Transformed error');
|
|
376
|
+
expect(data.original).toBe('Original error');
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('should handle multiple interceptors with error in middle', async () => {
|
|
380
|
+
const METADATA_KEY_1 = Symbol('test:error:1');
|
|
381
|
+
const METADATA_KEY_2 = Symbol('test:error:2');
|
|
382
|
+
const METADATA_KEY_3 = Symbol('test:error:3');
|
|
383
|
+
const executionOrder: string[] = [];
|
|
384
|
+
|
|
385
|
+
function createDecorator(key: symbol): MethodDecorator {
|
|
386
|
+
return (target, propertyKey) => {
|
|
387
|
+
Reflect.defineMetadata(key, true, target, propertyKey);
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const interceptor1: Interceptor = {
|
|
392
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
393
|
+
executionOrder.push('interceptor1');
|
|
394
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const interceptor2: Interceptor = {
|
|
399
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
400
|
+
executionOrder.push('interceptor2');
|
|
401
|
+
throw new Error('Error in interceptor2');
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const interceptor3: Interceptor = {
|
|
406
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
407
|
+
executionOrder.push('interceptor3');
|
|
408
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
interceptorRegistry.register(METADATA_KEY_1, interceptor1, 10);
|
|
413
|
+
interceptorRegistry.register(METADATA_KEY_2, interceptor2, 20);
|
|
414
|
+
interceptorRegistry.register(METADATA_KEY_3, interceptor3, 30);
|
|
415
|
+
|
|
416
|
+
@Controller('/api/test')
|
|
417
|
+
class TestController {
|
|
418
|
+
@GET('/')
|
|
419
|
+
@createDecorator(METADATA_KEY_1)
|
|
420
|
+
@createDecorator(METADATA_KEY_2)
|
|
421
|
+
@createDecorator(METADATA_KEY_3)
|
|
422
|
+
public test() {
|
|
423
|
+
executionOrder.push('method');
|
|
424
|
+
return { message: 'test' };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
app.registerController(TestController);
|
|
429
|
+
await app.listen();
|
|
430
|
+
|
|
431
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
432
|
+
expect(response.status).toBe(500);
|
|
433
|
+
// interceptor1 执行,interceptor2 抛出错误,interceptor3 和 method 不执行
|
|
434
|
+
expect(executionOrder).toEqual(['interceptor1', 'interceptor2']);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('should handle BaseInterceptor error handling', async () => {
|
|
438
|
+
const METADATA_KEY = Symbol('test:base-error');
|
|
439
|
+
|
|
440
|
+
function CustomDecorator(): MethodDecorator {
|
|
441
|
+
return (target, propertyKey) => {
|
|
442
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
class ErrorHandlingInterceptor extends BaseInterceptor {
|
|
447
|
+
public async execute<T>(
|
|
448
|
+
target: unknown,
|
|
449
|
+
propertyKey: string | symbol,
|
|
450
|
+
originalMethod: (...args: unknown[]) => T | Promise<T>,
|
|
451
|
+
args: unknown[],
|
|
452
|
+
container: Container,
|
|
453
|
+
context?: Context,
|
|
454
|
+
): Promise<T> {
|
|
455
|
+
try {
|
|
456
|
+
await this.before(target, propertyKey, args, container, context);
|
|
457
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
458
|
+
return await this.after(target, propertyKey, result, container, context) as T;
|
|
459
|
+
} catch (error) {
|
|
460
|
+
return await this.onError(target, propertyKey, error, container, context);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
protected async onError(
|
|
465
|
+
target: unknown,
|
|
466
|
+
propertyKey: string | symbol,
|
|
467
|
+
error: unknown,
|
|
468
|
+
container: Container,
|
|
469
|
+
context?: Context,
|
|
470
|
+
): Promise<never> {
|
|
471
|
+
// 转换错误为响应
|
|
472
|
+
throw new Error(`Handled: ${(error as Error).message}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const interceptor = new ErrorHandlingInterceptor();
|
|
477
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
478
|
+
|
|
479
|
+
@Controller('/api/test')
|
|
480
|
+
class TestController {
|
|
481
|
+
@GET('/')
|
|
482
|
+
@CustomDecorator()
|
|
483
|
+
public test() {
|
|
484
|
+
throw new Error('Original error');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
app.registerController(TestController);
|
|
489
|
+
await app.listen();
|
|
490
|
+
|
|
491
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
492
|
+
expect(response.status).toBe(500);
|
|
493
|
+
const data = await response.json();
|
|
494
|
+
expect(data.error).toBeDefined();
|
|
495
|
+
// 错误应该被 BaseInterceptor 的 onError 处理
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe('Complex Scenarios', () => {
|
|
500
|
+
test('should handle interceptors with async operations', async () => {
|
|
501
|
+
const METADATA_KEY = Symbol('test:async');
|
|
502
|
+
|
|
503
|
+
function CustomDecorator(): MethodDecorator {
|
|
504
|
+
return (target, propertyKey) => {
|
|
505
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
let asyncOperationCompleted = false;
|
|
510
|
+
|
|
511
|
+
const interceptor: Interceptor = {
|
|
512
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
513
|
+
// 模拟异步操作
|
|
514
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
515
|
+
asyncOperationCompleted = true;
|
|
516
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
521
|
+
|
|
522
|
+
@Controller('/api/test')
|
|
523
|
+
class TestController {
|
|
524
|
+
@GET('/')
|
|
525
|
+
@CustomDecorator()
|
|
526
|
+
public async test() {
|
|
527
|
+
return { message: 'test', asyncCompleted: asyncOperationCompleted };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
app.registerController(TestController);
|
|
532
|
+
await app.listen();
|
|
533
|
+
|
|
534
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
535
|
+
expect(response.status).toBe(200);
|
|
536
|
+
const data = await response.json();
|
|
537
|
+
expect(data.message).toBe('test');
|
|
538
|
+
expect(data.asyncCompleted).toBe(true);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test('should handle interceptors accessing container services', async () => {
|
|
542
|
+
const METADATA_KEY = Symbol('test:container');
|
|
543
|
+
|
|
544
|
+
function CustomDecorator(): MethodDecorator {
|
|
545
|
+
return (target, propertyKey) => {
|
|
546
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
class TestService {
|
|
551
|
+
public getValue(): string {
|
|
552
|
+
return 'service-value';
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const interceptor: Interceptor = {
|
|
557
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
558
|
+
// 从容器解析服务
|
|
559
|
+
const service = container.resolve<TestService>(TestService);
|
|
560
|
+
const value = service.getValue();
|
|
561
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
562
|
+
return { ...result, serviceValue: value };
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
567
|
+
|
|
568
|
+
// 注册服务到容器
|
|
569
|
+
const container = ControllerRegistry.getInstance().getContainer();
|
|
570
|
+
container.register(TestService);
|
|
571
|
+
|
|
572
|
+
@Controller('/api/test')
|
|
573
|
+
class TestController {
|
|
574
|
+
@GET('/')
|
|
575
|
+
@CustomDecorator()
|
|
576
|
+
public test() {
|
|
577
|
+
return { message: 'test' };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
app.registerController(TestController);
|
|
582
|
+
await app.listen();
|
|
583
|
+
|
|
584
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
585
|
+
expect(response.status).toBe(200);
|
|
586
|
+
const data = await response.json();
|
|
587
|
+
expect(data.message).toBe('test');
|
|
588
|
+
expect(data.serviceValue).toBe('service-value');
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { InterceptorChain } from '../../src/interceptor/interceptor-chain';
|
|
3
|
+
import type { Interceptor } from '../../src/interceptor';
|
|
4
|
+
import type { Container } from '../../src/di/container';
|
|
5
|
+
|
|
6
|
+
describe('InterceptorChain Argument Modification', () => {
|
|
7
|
+
const mockContainer = {} as Container;
|
|
8
|
+
|
|
9
|
+
test('should allow interceptors to modify arguments', async () => {
|
|
10
|
+
// Test interceptor that modifies arguments: multiply first argument by 2
|
|
11
|
+
const modifyArgsInterceptor: Interceptor = {
|
|
12
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
13
|
+
const modifiedArgs = [(args[0] as number) * 2, ...args.slice(1)];
|
|
14
|
+
return await Promise.resolve(originalMethod.apply(target, modifiedArgs));
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Test method that returns the first argument
|
|
19
|
+
function testMethod(x: number): number {
|
|
20
|
+
return x;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const interceptors: Interceptor[] = [modifyArgsInterceptor];
|
|
24
|
+
const result = await InterceptorChain.execute(
|
|
25
|
+
interceptors,
|
|
26
|
+
{},
|
|
27
|
+
'testMethod',
|
|
28
|
+
testMethod,
|
|
29
|
+
[5], // Original argument: 5
|
|
30
|
+
mockContainer,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Should return 10 (5 * 2) because interceptor modified the argument
|
|
34
|
+
expect(result).toBe(10);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should allow multiple interceptors to modify arguments sequentially', async () => {
|
|
38
|
+
// First interceptor: multiply by 2
|
|
39
|
+
const multiplyInterceptor: Interceptor = {
|
|
40
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
41
|
+
const modifiedArgs = [(args[0] as number) * 2, ...args.slice(1)];
|
|
42
|
+
return await Promise.resolve(originalMethod.apply(target, modifiedArgs));
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Second interceptor: add 3
|
|
47
|
+
const addInterceptor: Interceptor = {
|
|
48
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
49
|
+
const modifiedArgs = [(args[0] as number) + 3, ...args.slice(1)];
|
|
50
|
+
return await Promise.resolve(originalMethod.apply(target, modifiedArgs));
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Test method that returns the first argument
|
|
55
|
+
function testMethod(x: number): number {
|
|
56
|
+
return x;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const interceptors: Interceptor[] = [multiplyInterceptor, addInterceptor];
|
|
60
|
+
const result = await InterceptorChain.execute(
|
|
61
|
+
interceptors,
|
|
62
|
+
{},
|
|
63
|
+
'testMethod',
|
|
64
|
+
testMethod,
|
|
65
|
+
[5], // Original argument: 5
|
|
66
|
+
mockContainer,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Should return 13: (5 * 2) + 3 = 13
|
|
70
|
+
// Note: The order depends on how interceptors pass modified args
|
|
71
|
+
// In our implementation, each interceptor receives currentArgs and can modify them
|
|
72
|
+
// The last interceptor's modification takes effect
|
|
73
|
+
expect(result).toBe(13);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|