@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.
Files changed (77) hide show
  1. package/dist/controller/controller.d.ts +1 -1
  2. package/dist/controller/controller.d.ts.map +1 -1
  3. package/dist/core/application.d.ts.map +1 -1
  4. package/dist/database/database-extension.d.ts.map +1 -1
  5. package/dist/database/database-module.d.ts.map +1 -1
  6. package/dist/database/orm/transaction-decorator.d.ts +1 -0
  7. package/dist/database/orm/transaction-decorator.d.ts.map +1 -1
  8. package/dist/database/orm/transaction-interceptor.d.ts +12 -3
  9. package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +678 -310
  13. package/dist/interceptor/base-interceptor.d.ts +94 -0
  14. package/dist/interceptor/base-interceptor.d.ts.map +1 -0
  15. package/dist/interceptor/builtin/cache-interceptor.d.ts +69 -0
  16. package/dist/interceptor/builtin/cache-interceptor.d.ts.map +1 -0
  17. package/dist/interceptor/builtin/index.d.ts +4 -0
  18. package/dist/interceptor/builtin/index.d.ts.map +1 -0
  19. package/dist/interceptor/builtin/log-interceptor.d.ts +56 -0
  20. package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -0
  21. package/dist/interceptor/builtin/permission-interceptor.d.ts +70 -0
  22. package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -0
  23. package/dist/interceptor/index.d.ts +7 -0
  24. package/dist/interceptor/index.d.ts.map +1 -0
  25. package/dist/interceptor/interceptor-chain.d.ts +22 -0
  26. package/dist/interceptor/interceptor-chain.d.ts.map +1 -0
  27. package/dist/interceptor/interceptor-registry.d.ts +59 -0
  28. package/dist/interceptor/interceptor-registry.d.ts.map +1 -0
  29. package/dist/interceptor/metadata.d.ts +12 -0
  30. package/dist/interceptor/metadata.d.ts.map +1 -0
  31. package/dist/interceptor/types.d.ts +42 -0
  32. package/dist/interceptor/types.d.ts.map +1 -0
  33. package/dist/middleware/decorators.d.ts +2 -1
  34. package/dist/middleware/decorators.d.ts.map +1 -1
  35. package/dist/router/decorators.d.ts.map +1 -1
  36. package/dist/router/registry.d.ts +2 -1
  37. package/dist/router/registry.d.ts.map +1 -1
  38. package/dist/router/route.d.ts +3 -2
  39. package/dist/router/route.d.ts.map +1 -1
  40. package/dist/router/router.d.ts +2 -1
  41. package/dist/router/router.d.ts.map +1 -1
  42. package/dist/websocket/decorators.d.ts +2 -1
  43. package/dist/websocket/decorators.d.ts.map +1 -1
  44. package/package.json +2 -2
  45. package/src/controller/controller.ts +41 -14
  46. package/src/core/application.ts +7 -1
  47. package/src/database/database-extension.ts +23 -2
  48. package/src/database/database-module.ts +6 -0
  49. package/src/database/orm/transaction-decorator.ts +33 -2
  50. package/src/database/orm/transaction-interceptor.ts +31 -11
  51. package/src/index.ts +22 -0
  52. package/src/interceptor/base-interceptor.ts +203 -0
  53. package/src/interceptor/builtin/cache-interceptor.ts +169 -0
  54. package/src/interceptor/builtin/index.ts +28 -0
  55. package/src/interceptor/builtin/log-interceptor.ts +178 -0
  56. package/src/interceptor/builtin/permission-interceptor.ts +173 -0
  57. package/src/interceptor/index.ts +26 -0
  58. package/src/interceptor/interceptor-chain.ts +79 -0
  59. package/src/interceptor/interceptor-registry.ts +132 -0
  60. package/src/interceptor/metadata.ts +40 -0
  61. package/src/interceptor/types.ts +52 -0
  62. package/src/middleware/decorators.ts +2 -1
  63. package/src/router/decorators.ts +44 -43
  64. package/src/router/registry.ts +2 -1
  65. package/src/router/route.ts +3 -2
  66. package/src/router/router.ts +2 -1
  67. package/src/websocket/decorators.ts +3 -1
  68. package/tests/controller/path-combination.test.ts +207 -0
  69. package/tests/interceptor/builtin/cache-interceptor.test.ts +137 -0
  70. package/tests/interceptor/builtin/permission-interceptor.test.ts +182 -0
  71. package/tests/interceptor/interceptor-advanced-integration.test.ts +592 -0
  72. package/tests/interceptor/interceptor-arg-modification.test.ts +76 -0
  73. package/tests/interceptor/interceptor-chain.test.ts +199 -0
  74. package/tests/interceptor/interceptor-integration.test.ts +230 -0
  75. package/tests/interceptor/interceptor-registry.test.ts +200 -0
  76. package/tests/interceptor/perf/interceptor-performance.test.ts +341 -0
  77. package/tests/router/decorators.test.ts +13 -15
@@ -0,0 +1,137 @@
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 } from '../../../src/router/decorators';
6
+ import { RouteRegistry } from '../../../src/router/registry';
7
+ import {
8
+ Cache,
9
+ CacheInterceptor,
10
+ CACHE_METADATA_KEY,
11
+ InterceptorRegistry,
12
+ INTERCEPTOR_REGISTRY_TOKEN,
13
+ } from '../../../src/interceptor';
14
+ import { getTestPort } from '../../utils/test-port';
15
+
16
+ describe('CacheInterceptor', () => {
17
+ let app: Application;
18
+ let port: number;
19
+ let interceptorRegistry: InterceptorRegistry;
20
+ let callCount = 0;
21
+
22
+ beforeEach(() => {
23
+ port = getTestPort();
24
+ app = new Application({ port });
25
+ interceptorRegistry = app.getContainer().resolve<InterceptorRegistry>(
26
+ INTERCEPTOR_REGISTRY_TOKEN,
27
+ );
28
+ interceptorRegistry.register(CACHE_METADATA_KEY, new CacheInterceptor());
29
+ callCount = 0;
30
+ CacheInterceptor.clearCache();
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
+ CacheInterceptor.clearCache();
41
+ });
42
+
43
+ test('should cache method result', async () => {
44
+ @Controller('/api/test')
45
+ class TestController {
46
+ @GET('/')
47
+ @Cache({ ttl: 1000 })
48
+ public getData() {
49
+ callCount++;
50
+ return { data: 'cached', count: callCount };
51
+ }
52
+ }
53
+
54
+ app.registerController(TestController);
55
+ await app.listen();
56
+
57
+ // 第一次调用
58
+ const response1 = await fetch(`http://localhost:${port}/api/test`);
59
+ expect(response1.status).toBe(200);
60
+ const data1 = await response1.json();
61
+ expect(data1.count).toBe(1);
62
+ expect(callCount).toBe(1);
63
+
64
+ // 第二次调用(应该使用缓存)
65
+ const response2 = await fetch(`http://localhost:${port}/api/test`);
66
+ expect(response2.status).toBe(200);
67
+ const data2 = await response2.json();
68
+ expect(data2.count).toBe(1); // 缓存的结果
69
+ expect(callCount).toBe(1); // 方法没有被再次调用
70
+ });
71
+
72
+ test('should use custom cache key', async () => {
73
+ @Controller('/api/test')
74
+ class TestController {
75
+ @GET('/')
76
+ @Cache({ ttl: 1000, key: 'custom-key' })
77
+ public getData() {
78
+ callCount++;
79
+ return { data: 'cached' };
80
+ }
81
+ }
82
+
83
+ app.registerController(TestController);
84
+ await app.listen();
85
+
86
+ await fetch(`http://localhost:${port}/api/test`);
87
+ await fetch(`http://localhost:${port}/api/test`);
88
+
89
+ expect(callCount).toBe(1); // 应该只调用一次(使用缓存)
90
+ });
91
+
92
+ test('should expire cache after TTL', async () => {
93
+ @Controller('/api/test')
94
+ class TestController {
95
+ @GET('/')
96
+ @Cache({ ttl: 100 }) // 100ms TTL
97
+ public getData() {
98
+ callCount++;
99
+ return { data: 'cached', count: callCount };
100
+ }
101
+ }
102
+
103
+ app.registerController(TestController);
104
+ await app.listen();
105
+
106
+ // 第一次调用
107
+ await fetch(`http://localhost:${port}/api/test`);
108
+ expect(callCount).toBe(1);
109
+
110
+ // 等待缓存过期
111
+ await new Promise((resolve) => setTimeout(resolve, 150));
112
+
113
+ // 第二次调用(缓存已过期)
114
+ await fetch(`http://localhost:${port}/api/test`);
115
+ expect(callCount).toBe(2);
116
+ });
117
+
118
+ test('should work without cache decorator', async () => {
119
+ @Controller('/api/test')
120
+ class TestController {
121
+ @GET('/')
122
+ public getData() {
123
+ callCount++;
124
+ return { data: 'no-cache' };
125
+ }
126
+ }
127
+
128
+ app.registerController(TestController);
129
+ await app.listen();
130
+
131
+ await fetch(`http://localhost:${port}/api/test`);
132
+ await fetch(`http://localhost:${port}/api/test`);
133
+
134
+ expect(callCount).toBe(2); // 没有缓存,应该调用两次
135
+ });
136
+ });
137
+
@@ -0,0 +1,182 @@
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 } from '../../../src/router/decorators';
6
+ import { RouteRegistry } from '../../../src/router/registry';
7
+ import {
8
+ Permission,
9
+ PermissionInterceptor,
10
+ PERMISSION_METADATA_KEY,
11
+ InterceptorRegistry,
12
+ INTERCEPTOR_REGISTRY_TOKEN,
13
+ type PermissionService,
14
+ } from '../../../src/interceptor';
15
+ import { ForbiddenException } from '../../../src/error';
16
+ import { getTestPort } from '../../utils/test-port';
17
+
18
+ describe('PermissionInterceptor', () => {
19
+ let app: Application;
20
+ let port: number;
21
+ let interceptorRegistry: InterceptorRegistry;
22
+
23
+ beforeEach(() => {
24
+ port = getTestPort();
25
+ app = new Application({ port });
26
+ interceptorRegistry = app.getContainer().resolve<InterceptorRegistry>(
27
+ INTERCEPTOR_REGISTRY_TOKEN,
28
+ );
29
+ interceptorRegistry.register(PERMISSION_METADATA_KEY, new PermissionInterceptor());
30
+ });
31
+
32
+ afterEach(async () => {
33
+ if (app) {
34
+ await app.stop();
35
+ }
36
+ RouteRegistry.getInstance().clear();
37
+ ControllerRegistry.getInstance().clear();
38
+ interceptorRegistry.clear();
39
+ });
40
+
41
+ test('should allow access when permission check passes', async () => {
42
+ // 创建权限服务实现
43
+ class MockPermissionService implements PermissionService {
44
+ public async check(
45
+ userId: string | null,
46
+ resource: string,
47
+ action: string,
48
+ ): Promise<boolean> {
49
+ return userId === 'user1' && resource === 'user' && action === 'read';
50
+ }
51
+ }
52
+
53
+ // 注册权限服务
54
+ app.getContainer().registerInstance(
55
+ PermissionInterceptor.PERMISSION_SERVICE_TOKEN,
56
+ new MockPermissionService(),
57
+ );
58
+
59
+ @Controller('/api/users')
60
+ class UserController {
61
+ @GET('/:id')
62
+ @Permission({ resource: 'user', action: 'read' })
63
+ public getUser() {
64
+ return { id: '123', name: 'Test User' };
65
+ }
66
+ }
67
+
68
+ app.registerController(UserController);
69
+ await app.listen();
70
+
71
+ const response = await fetch(`http://localhost:${port}/api/users/123`, {
72
+ headers: { 'X-User-Id': 'user1' },
73
+ });
74
+
75
+ expect(response.status).toBe(200);
76
+ const data = await response.json();
77
+ expect(data.id).toBe('123');
78
+ });
79
+
80
+ test('should deny access when permission check fails', async () => {
81
+ // 创建权限服务实现
82
+ class MockPermissionService implements PermissionService {
83
+ public async check(
84
+ userId: string | null,
85
+ resource: string,
86
+ action: string,
87
+ ): Promise<boolean> {
88
+ return false; // 总是拒绝
89
+ }
90
+ }
91
+
92
+ // 注册权限服务
93
+ app.getContainer().registerInstance(
94
+ PermissionInterceptor.PERMISSION_SERVICE_TOKEN,
95
+ new MockPermissionService(),
96
+ );
97
+
98
+ @Controller('/api/users')
99
+ class UserController {
100
+ @GET('/:id')
101
+ @Permission({ resource: 'user', action: 'read' })
102
+ public getUser() {
103
+ return { id: '123', name: 'Test User' };
104
+ }
105
+ }
106
+
107
+ app.registerController(UserController);
108
+ await app.listen();
109
+
110
+ const response = await fetch(`http://localhost:${port}/api/users/123`, {
111
+ headers: { 'X-User-Id': 'user1' },
112
+ });
113
+
114
+ expect(response.status).toBe(403);
115
+ });
116
+
117
+ test('should allow anonymous access when allowAnonymous is true', async () => {
118
+ @Controller('/api/public')
119
+ class PublicController {
120
+ @GET('/')
121
+ @Permission({
122
+ resource: 'public',
123
+ action: 'read',
124
+ allowAnonymous: true,
125
+ })
126
+ public getPublicData() {
127
+ return { data: 'public' };
128
+ }
129
+ }
130
+
131
+ app.registerController(PublicController);
132
+ await app.listen();
133
+
134
+ // 没有提供用户 ID
135
+ const response = await fetch(`http://localhost:${port}/api/public`);
136
+
137
+ expect(response.status).toBe(200);
138
+ const data = await response.json();
139
+ expect(data.data).toBe('public');
140
+ });
141
+
142
+ test('should work without permission decorator', async () => {
143
+ @Controller('/api/test')
144
+ class TestController {
145
+ @GET('/')
146
+ public getData() {
147
+ return { data: 'no-permission-check' };
148
+ }
149
+ }
150
+
151
+ app.registerController(TestController);
152
+ await app.listen();
153
+
154
+ const response = await fetch(`http://localhost:${port}/api/test`);
155
+
156
+ expect(response.status).toBe(200);
157
+ const data = await response.json();
158
+ expect(data.data).toBe('no-permission-check');
159
+ });
160
+
161
+ test('should throw error when PermissionService not registered', async () => {
162
+ @Controller('/api/users')
163
+ class UserController {
164
+ @GET('/:id')
165
+ @Permission({ resource: 'user', action: 'read' })
166
+ public getUser() {
167
+ return { id: '123' };
168
+ }
169
+ }
170
+
171
+ app.registerController(UserController);
172
+ await app.listen();
173
+
174
+ const response = await fetch(`http://localhost:${port}/api/users/123`, {
175
+ headers: { 'X-User-Id': 'user1' },
176
+ });
177
+
178
+ // 应该返回 500 错误(PermissionService 未注册)
179
+ expect(response.status).toBe(500);
180
+ });
181
+ });
182
+