@dangao/bun-server 1.0.3 → 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 +2 -2
- package/src/controller/controller.ts +41 -14
- package/src/core/application.ts +7 -1
- package/src/database/database-extension.ts +23 -2
- package/src/database/database-module.ts +6 -0
- package/src/database/orm/transaction-decorator.ts +33 -2
- package/src/database/orm/transaction-interceptor.ts +31 -11
- package/src/index.ts +22 -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/middleware/decorators.ts +2 -1
- package/src/router/decorators.ts +44 -43
- package/src/router/registry.ts +2 -1
- package/src/router/route.ts +3 -2
- package/src/router/router.ts +2 -1
- package/src/websocket/decorators.ts +3 -1
- package/tests/controller/path-combination.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/router/decorators.test.ts +13 -15
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
|
|
4
|
+
import { PerformanceHarness } from '../../../src/testing/harness';
|
|
5
|
+
import { InterceptorRegistry } from '../../../src/interceptor/interceptor-registry';
|
|
6
|
+
import { InterceptorChain } from '../../../src/interceptor/interceptor-chain';
|
|
7
|
+
import type { Interceptor } from '../../../src/interceptor/types';
|
|
8
|
+
import type { Container } from '../../../src/di/container';
|
|
9
|
+
import type { Context } from '../../../src/core/context';
|
|
10
|
+
import { Container as DIContainer } from '../../../src/di/container';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 简单的无操作拦截器,用于性能测试
|
|
14
|
+
*/
|
|
15
|
+
class NoOpInterceptor implements Interceptor {
|
|
16
|
+
public async execute<T>(
|
|
17
|
+
target: unknown,
|
|
18
|
+
propertyKey: string | symbol,
|
|
19
|
+
originalMethod: (...args: unknown[]) => T | Promise<T>,
|
|
20
|
+
args: unknown[],
|
|
21
|
+
container: Container,
|
|
22
|
+
context?: Context,
|
|
23
|
+
): Promise<T> {
|
|
24
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 简单的同步拦截器,用于性能测试
|
|
30
|
+
*/
|
|
31
|
+
class SyncInterceptor implements Interceptor {
|
|
32
|
+
public async execute<T>(
|
|
33
|
+
target: unknown,
|
|
34
|
+
propertyKey: string | symbol,
|
|
35
|
+
originalMethod: (...args: unknown[]) => T | Promise<T>,
|
|
36
|
+
args: unknown[],
|
|
37
|
+
container: Container,
|
|
38
|
+
context?: Context,
|
|
39
|
+
): Promise<T> {
|
|
40
|
+
// 简单的同步操作
|
|
41
|
+
const _ = propertyKey.toString();
|
|
42
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 测试目标类
|
|
48
|
+
*/
|
|
49
|
+
class TestTarget {
|
|
50
|
+
public value = 0;
|
|
51
|
+
|
|
52
|
+
public async method(arg: number): Promise<number> {
|
|
53
|
+
this.value += arg;
|
|
54
|
+
return this.value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public syncMethod(arg: number): number {
|
|
58
|
+
this.value += arg;
|
|
59
|
+
return this.value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe('Interceptor Performance', () => {
|
|
64
|
+
const container = new DIContainer();
|
|
65
|
+
const registry = new InterceptorRegistry();
|
|
66
|
+
const metadataKey = Symbol('test');
|
|
67
|
+
|
|
68
|
+
describe('Single Interceptor Performance', () => {
|
|
69
|
+
test('should benchmark single interceptor execution', async () => {
|
|
70
|
+
const interceptor = new NoOpInterceptor();
|
|
71
|
+
const target = new TestTarget();
|
|
72
|
+
const originalMethod = target.method.bind(target);
|
|
73
|
+
|
|
74
|
+
const result = await PerformanceHarness.benchmark(
|
|
75
|
+
'single-interceptor',
|
|
76
|
+
10000,
|
|
77
|
+
async () => {
|
|
78
|
+
await interceptor.execute(
|
|
79
|
+
target,
|
|
80
|
+
'method',
|
|
81
|
+
originalMethod,
|
|
82
|
+
[1],
|
|
83
|
+
container,
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(result.iterations).toBe(10000);
|
|
89
|
+
expect(result.opsPerSecond).toBeGreaterThan(1000);
|
|
90
|
+
expect(result.durationMs).toBeLessThan(10000);
|
|
91
|
+
|
|
92
|
+
console.log(`Single Interceptor: ${result.opsPerSecond.toFixed(2)} ops/sec`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('should benchmark interceptor chain with 1 interceptor', async () => {
|
|
96
|
+
const interceptor = new NoOpInterceptor();
|
|
97
|
+
const target = new TestTarget();
|
|
98
|
+
const originalMethod = target.method.bind(target);
|
|
99
|
+
|
|
100
|
+
const result = await PerformanceHarness.benchmark(
|
|
101
|
+
'chain-1-interceptor',
|
|
102
|
+
10000,
|
|
103
|
+
async () => {
|
|
104
|
+
await InterceptorChain.execute(
|
|
105
|
+
[interceptor],
|
|
106
|
+
target,
|
|
107
|
+
'method',
|
|
108
|
+
originalMethod,
|
|
109
|
+
[1],
|
|
110
|
+
container,
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(result.iterations).toBe(10000);
|
|
116
|
+
expect(result.opsPerSecond).toBeGreaterThan(1000);
|
|
117
|
+
expect(result.durationMs).toBeLessThan(10000);
|
|
118
|
+
|
|
119
|
+
console.log(`Chain (1 interceptor): ${result.opsPerSecond.toFixed(2)} ops/sec`);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Interceptor Chain Performance', () => {
|
|
124
|
+
test('should benchmark interceptor chain with 3 interceptors', async () => {
|
|
125
|
+
const interceptors = [
|
|
126
|
+
new NoOpInterceptor(),
|
|
127
|
+
new NoOpInterceptor(),
|
|
128
|
+
new NoOpInterceptor(),
|
|
129
|
+
];
|
|
130
|
+
const target = new TestTarget();
|
|
131
|
+
const originalMethod = target.method.bind(target);
|
|
132
|
+
|
|
133
|
+
const result = await PerformanceHarness.benchmark(
|
|
134
|
+
'chain-3-interceptors',
|
|
135
|
+
10000,
|
|
136
|
+
async () => {
|
|
137
|
+
await InterceptorChain.execute(
|
|
138
|
+
interceptors,
|
|
139
|
+
target,
|
|
140
|
+
'method',
|
|
141
|
+
originalMethod,
|
|
142
|
+
[1],
|
|
143
|
+
container,
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(result.iterations).toBe(10000);
|
|
149
|
+
expect(result.opsPerSecond).toBeGreaterThan(500);
|
|
150
|
+
expect(result.durationMs).toBeLessThan(20000);
|
|
151
|
+
|
|
152
|
+
console.log(`Chain (3 interceptors): ${result.opsPerSecond.toFixed(2)} ops/sec`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('should benchmark interceptor chain with 5 interceptors', async () => {
|
|
156
|
+
const interceptors = Array.from({ length: 5 }, () => new NoOpInterceptor());
|
|
157
|
+
const target = new TestTarget();
|
|
158
|
+
const originalMethod = target.method.bind(target);
|
|
159
|
+
|
|
160
|
+
const result = await PerformanceHarness.benchmark(
|
|
161
|
+
'chain-5-interceptors',
|
|
162
|
+
10000,
|
|
163
|
+
async () => {
|
|
164
|
+
await InterceptorChain.execute(
|
|
165
|
+
interceptors,
|
|
166
|
+
target,
|
|
167
|
+
'method',
|
|
168
|
+
originalMethod,
|
|
169
|
+
[1],
|
|
170
|
+
container,
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(result.iterations).toBe(10000);
|
|
176
|
+
expect(result.opsPerSecond).toBeGreaterThan(300);
|
|
177
|
+
expect(result.durationMs).toBeLessThan(35000);
|
|
178
|
+
|
|
179
|
+
console.log(`Chain (5 interceptors): ${result.opsPerSecond.toFixed(2)} ops/sec`);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('should benchmark interceptor chain with 10 interceptors', async () => {
|
|
183
|
+
const interceptors = Array.from({ length: 10 }, () => new NoOpInterceptor());
|
|
184
|
+
const target = new TestTarget();
|
|
185
|
+
const originalMethod = target.method.bind(target);
|
|
186
|
+
|
|
187
|
+
const result = await PerformanceHarness.benchmark(
|
|
188
|
+
'chain-10-interceptors',
|
|
189
|
+
10000,
|
|
190
|
+
async () => {
|
|
191
|
+
await InterceptorChain.execute(
|
|
192
|
+
interceptors,
|
|
193
|
+
target,
|
|
194
|
+
'method',
|
|
195
|
+
originalMethod,
|
|
196
|
+
[1],
|
|
197
|
+
container,
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(result.iterations).toBe(10000);
|
|
203
|
+
expect(result.opsPerSecond).toBeGreaterThan(200);
|
|
204
|
+
expect(result.durationMs).toBeLessThan(50000);
|
|
205
|
+
|
|
206
|
+
console.log(`Chain (10 interceptors): ${result.opsPerSecond.toFixed(2)} ops/sec`);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('Performance Comparison', () => {
|
|
211
|
+
test('should compare direct method call vs interceptor chain', async () => {
|
|
212
|
+
const target = new TestTarget();
|
|
213
|
+
const originalMethod = target.method.bind(target);
|
|
214
|
+
const interceptors = [new NoOpInterceptor()];
|
|
215
|
+
|
|
216
|
+
// 直接方法调用
|
|
217
|
+
const directResult = await PerformanceHarness.benchmark(
|
|
218
|
+
'direct-method-call',
|
|
219
|
+
10000,
|
|
220
|
+
async () => {
|
|
221
|
+
await originalMethod(1);
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// 拦截器链调用
|
|
226
|
+
const interceptorResult = await PerformanceHarness.benchmark(
|
|
227
|
+
'interceptor-chain',
|
|
228
|
+
10000,
|
|
229
|
+
async () => {
|
|
230
|
+
await InterceptorChain.execute(
|
|
231
|
+
interceptors,
|
|
232
|
+
target,
|
|
233
|
+
'method',
|
|
234
|
+
originalMethod,
|
|
235
|
+
[1],
|
|
236
|
+
container,
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(directResult.iterations).toBe(10000);
|
|
242
|
+
expect(interceptorResult.iterations).toBe(10000);
|
|
243
|
+
|
|
244
|
+
const overhead = (interceptorResult.durationMs / directResult.durationMs - 1) * 100;
|
|
245
|
+
|
|
246
|
+
console.log(`Direct call: ${directResult.opsPerSecond.toFixed(2)} ops/sec`);
|
|
247
|
+
console.log(`Interceptor chain: ${interceptorResult.opsPerSecond.toFixed(2)} ops/sec`);
|
|
248
|
+
console.log(`Overhead: ${overhead.toFixed(2)}%`);
|
|
249
|
+
|
|
250
|
+
// 拦截器开销应该小于 400%(即不超过 5 倍)
|
|
251
|
+
// 实际测试显示开销约为 275%,这是合理的,因为需要额外的函数调用和参数传递
|
|
252
|
+
expect(overhead).toBeLessThan(400);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('should compare sync method call vs interceptor chain', async () => {
|
|
256
|
+
const target = new TestTarget();
|
|
257
|
+
const originalMethod = target.syncMethod.bind(target);
|
|
258
|
+
const interceptors = [new SyncInterceptor()];
|
|
259
|
+
|
|
260
|
+
// 直接方法调用
|
|
261
|
+
const directResult = await PerformanceHarness.benchmark(
|
|
262
|
+
'direct-sync-call',
|
|
263
|
+
100000,
|
|
264
|
+
async () => {
|
|
265
|
+
originalMethod(1);
|
|
266
|
+
},
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// 拦截器链调用
|
|
270
|
+
const interceptorResult = await PerformanceHarness.benchmark(
|
|
271
|
+
'interceptor-sync-chain',
|
|
272
|
+
100000,
|
|
273
|
+
async () => {
|
|
274
|
+
await InterceptorChain.execute(
|
|
275
|
+
interceptors,
|
|
276
|
+
target,
|
|
277
|
+
'syncMethod',
|
|
278
|
+
originalMethod,
|
|
279
|
+
[1],
|
|
280
|
+
container,
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(directResult.iterations).toBe(100000);
|
|
286
|
+
expect(interceptorResult.iterations).toBe(100000);
|
|
287
|
+
|
|
288
|
+
const overhead = (interceptorResult.durationMs / directResult.durationMs - 1) * 100;
|
|
289
|
+
|
|
290
|
+
console.log(`Direct sync call: ${directResult.opsPerSecond.toFixed(2)} ops/sec`);
|
|
291
|
+
console.log(`Interceptor sync chain: ${interceptorResult.opsPerSecond.toFixed(2)} ops/sec`);
|
|
292
|
+
console.log(`Overhead: ${overhead.toFixed(2)}%`);
|
|
293
|
+
|
|
294
|
+
// 拦截器开销应该小于 1000%(即不超过 11 倍)
|
|
295
|
+
// 同步方法转换为异步会有额外开销,实际测试显示开销约为 656-870%
|
|
296
|
+
// 性能测试结果可能有波动,使用更宽松的阈值以确保测试稳定性
|
|
297
|
+
expect(overhead).toBeLessThan(1000);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('Registry Performance', () => {
|
|
302
|
+
test('should benchmark interceptor registry operations', async () => {
|
|
303
|
+
const result = await PerformanceHarness.benchmark(
|
|
304
|
+
'registry-register',
|
|
305
|
+
10000,
|
|
306
|
+
async (iteration) => {
|
|
307
|
+
const key = Symbol(`test-${iteration}`);
|
|
308
|
+
registry.register(key, new NoOpInterceptor(), 100);
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(result.iterations).toBe(10000);
|
|
313
|
+
expect(result.opsPerSecond).toBeGreaterThan(1000);
|
|
314
|
+
|
|
315
|
+
console.log(`Registry register: ${result.opsPerSecond.toFixed(2)} ops/sec`);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('should benchmark interceptor registry lookup', async () => {
|
|
319
|
+
// 预先注册一些拦截器
|
|
320
|
+
const keys = Array.from({ length: 100 }, (_, i) => Symbol(`key-${i}`));
|
|
321
|
+
for (const key of keys) {
|
|
322
|
+
registry.register(key, new NoOpInterceptor(), 100);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const result = await PerformanceHarness.benchmark(
|
|
326
|
+
'registry-lookup',
|
|
327
|
+
10000,
|
|
328
|
+
async (iteration) => {
|
|
329
|
+
const key = keys[iteration % keys.length];
|
|
330
|
+
registry.getInterceptors(key);
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
expect(result.iterations).toBe(10000);
|
|
335
|
+
expect(result.opsPerSecond).toBeGreaterThan(5000);
|
|
336
|
+
|
|
337
|
+
console.log(`Registry lookup: ${result.opsPerSecond.toFixed(2)} ops/sec`);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
2
|
|
|
3
|
-
import { Controller } from '../../src/controller/controller';
|
|
4
|
-
import { GET
|
|
3
|
+
import { Controller, ControllerRegistry } from '../../src/controller/controller';
|
|
4
|
+
import { GET } from '../../src/router/decorators';
|
|
5
5
|
import { getRouteMetadata } from '../../src/controller/metadata';
|
|
6
6
|
import { RouteRegistry } from '../../src/router/registry';
|
|
7
|
-
import { Context } from '../../src/core/context';
|
|
8
7
|
|
|
9
8
|
describe('Router Decorators', () => {
|
|
10
9
|
afterEach(() => {
|
|
11
10
|
RouteRegistry.getInstance().clear();
|
|
11
|
+
ControllerRegistry.getInstance().clear();
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
test('should record metadata for controller methods', () => {
|
|
@@ -27,22 +27,20 @@ describe('Router Decorators', () => {
|
|
|
27
27
|
expect(typeof metadata[0]?.handler).toBe('function');
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
test('should
|
|
30
|
+
test('should throw error when registering class without @Controller decorator', () => {
|
|
31
|
+
// 注意:由于装饰器应用顺序,@GET 装饰器会保存元数据,不会立即报错
|
|
31
32
|
class PlainHandlers {
|
|
32
|
-
@
|
|
33
|
-
public handler(
|
|
34
|
-
return
|
|
33
|
+
@GET('/plain')
|
|
34
|
+
public handler() {
|
|
35
|
+
return { ok: true };
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
expect(response).toBeDefined();
|
|
45
|
-
expect(await response?.json()).toEqual({ ok: true });
|
|
39
|
+
// 但尝试注册时会报错,因为类没有 @Controller 装饰器
|
|
40
|
+
const registry = ControllerRegistry.getInstance();
|
|
41
|
+
expect(() => {
|
|
42
|
+
registry.register(PlainHandlers);
|
|
43
|
+
}).toThrow('Controller PlainHandlers must be decorated with @Controller()');
|
|
46
44
|
});
|
|
47
45
|
});
|
|
48
46
|
|