@dangao/bun-server 1.8.0 → 1.8.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 (62) hide show
  1. package/docs/api.md +194 -81
  2. package/docs/extensions.md +53 -0
  3. package/docs/guide.md +243 -1
  4. package/docs/microservice-config-center.md +73 -74
  5. package/docs/microservice-nacos.md +89 -90
  6. package/docs/microservice-service-registry.md +85 -86
  7. package/docs/microservice.md +142 -137
  8. package/docs/request-lifecycle.md +45 -4
  9. package/docs/symbol-interface-pattern.md +106 -106
  10. package/docs/zh/api.md +458 -18
  11. package/docs/zh/extensions.md +53 -0
  12. package/docs/zh/guide.md +251 -4
  13. package/docs/zh/microservice-config-center.md +258 -0
  14. package/docs/zh/microservice-nacos.md +346 -0
  15. package/docs/zh/microservice-service-registry.md +306 -0
  16. package/docs/zh/microservice.md +680 -0
  17. package/docs/zh/request-lifecycle.md +43 -5
  18. package/package.json +1 -1
  19. package/tests/auth/auth-decorators.test.ts +241 -0
  20. package/tests/auth/oauth2-service.test.ts +318 -0
  21. package/tests/cache/cache-decorators-extended.test.ts +272 -0
  22. package/tests/cache/cache-interceptors.test.ts +534 -0
  23. package/tests/cache/cache-service-proxy.test.ts +246 -0
  24. package/tests/cache/memory-cache-store.test.ts +155 -0
  25. package/tests/cache/redis-cache-store.test.ts +199 -0
  26. package/tests/config/config-center-integration.test.ts +334 -0
  27. package/tests/config/config-module-extended.test.ts +165 -0
  28. package/tests/controller/param-binder.test.ts +333 -0
  29. package/tests/error/error-handler.test.ts +166 -57
  30. package/tests/error/i18n-extended.test.ts +105 -0
  31. package/tests/events/event-listener-scanner.test.ts +114 -0
  32. package/tests/events/event-module.test.ts +133 -302
  33. package/tests/extensions/logger-module.test.ts +158 -0
  34. package/tests/files/file-storage.test.ts +136 -0
  35. package/tests/interceptor/base-interceptor.test.ts +605 -0
  36. package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
  37. package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
  38. package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
  39. package/tests/interceptor/interceptor-chain.test.ts +241 -189
  40. package/tests/interceptor/interceptor-metadata.test.ts +221 -0
  41. package/tests/microservice/circuit-breaker.test.ts +221 -0
  42. package/tests/microservice/service-client-decorators.test.ts +86 -0
  43. package/tests/microservice/service-client-interceptors.test.ts +274 -0
  44. package/tests/microservice/service-registry-decorators.test.ts +147 -0
  45. package/tests/microservice/tracer.test.ts +213 -0
  46. package/tests/microservice/tracing-collectors.test.ts +168 -0
  47. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
  48. package/tests/middleware/builtin/rate-limit.test.ts +257 -0
  49. package/tests/middleware/middleware-decorators.test.ts +222 -0
  50. package/tests/middleware/middleware-pipeline.test.ts +160 -0
  51. package/tests/queue/queue-decorators.test.ts +139 -0
  52. package/tests/queue/queue-service.test.ts +191 -0
  53. package/tests/request/body-parser-extended.test.ts +291 -0
  54. package/tests/request/request-wrapper.test.ts +319 -0
  55. package/tests/router/router-decorators.test.ts +260 -0
  56. package/tests/router/router-extended.test.ts +298 -0
  57. package/tests/security/guards/reflector.test.ts +188 -0
  58. package/tests/security/security-filter.test.ts +182 -0
  59. package/tests/security/security-module-extended.test.ts +133 -0
  60. package/tests/session/memory-session-store.test.ts +172 -0
  61. package/tests/session/session-decorators.test.ts +163 -0
  62. package/tests/swagger/ui.test.ts +212 -0
@@ -0,0 +1,334 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { MODULE_METADATA_KEY } from '../../src/di/module';
5
+ import {
6
+ ConfigModule,
7
+ ConfigService,
8
+ CONFIG_SERVICE_TOKEN,
9
+ type ConfigModuleOptions,
10
+ } from '../../src/config';
11
+
12
+ describe('ConfigModule setValueByPath', () => {
13
+ beforeEach(() => {
14
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, ConfigModule);
15
+ });
16
+
17
+ test('should set value at root level', () => {
18
+ const obj: Record<string, unknown> = {};
19
+ (ConfigModule as any).setValueByPath(obj, 'key', 'value');
20
+
21
+ expect(obj.key).toBe('value');
22
+ });
23
+
24
+ test('should set value at nested path', () => {
25
+ const obj: Record<string, unknown> = {};
26
+ (ConfigModule as any).setValueByPath(obj, 'a.b.c', 'nested');
27
+
28
+ expect((obj as any).a.b.c).toBe('nested');
29
+ });
30
+
31
+ test('should create intermediate objects', () => {
32
+ const obj: Record<string, unknown> = {};
33
+ (ConfigModule as any).setValueByPath(obj, 'level1.level2.level3', 'deep');
34
+
35
+ expect(obj.level1).toBeDefined();
36
+ expect((obj.level1 as any).level2).toBeDefined();
37
+ expect((obj.level1 as any).level2.level3).toBe('deep');
38
+ });
39
+
40
+ test('should override non-object values in path', () => {
41
+ const obj: Record<string, unknown> = { a: 'string' };
42
+ (ConfigModule as any).setValueByPath(obj, 'a.b', 'value');
43
+
44
+ expect((obj as any).a.b).toBe('value');
45
+ });
46
+
47
+ test('should handle null values in path', () => {
48
+ const obj: Record<string, unknown> = { a: null };
49
+ (ConfigModule as any).setValueByPath(obj, 'a.b', 'value');
50
+
51
+ expect((obj as any).a.b).toBe('value');
52
+ });
53
+ });
54
+
55
+ describe('ConfigModule configCenter options', () => {
56
+ beforeEach(() => {
57
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, ConfigModule);
58
+ });
59
+
60
+ afterEach(() => {
61
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, ConfigModule);
62
+ });
63
+
64
+ test('should enable config center integration when enabled is true', () => {
65
+ const options: ConfigModuleOptions = {
66
+ defaultConfig: { app: { name: 'test' } },
67
+ configCenter: {
68
+ enabled: true,
69
+ },
70
+ };
71
+
72
+ ConfigModule.forRoot(options);
73
+
74
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
75
+ const configProvider = metadata.providers.find(
76
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
77
+ );
78
+ const service = configProvider.useValue as ConfigService;
79
+
80
+ // 检查配置中心选项被保存
81
+ expect((service as any)._configCenterOptions).toBeDefined();
82
+ expect((service as any)._configCenterOptions.enabled).toBe(true);
83
+ });
84
+
85
+ test('should not set config center options when not enabled', () => {
86
+ const options: ConfigModuleOptions = {
87
+ defaultConfig: { app: { name: 'test' } },
88
+ };
89
+
90
+ ConfigModule.forRoot(options);
91
+
92
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
93
+ const configProvider = metadata.providers.find(
94
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
95
+ );
96
+ const service = configProvider.useValue as ConfigService;
97
+
98
+ expect((service as any)._configCenterOptions).toBeUndefined();
99
+ });
100
+
101
+ test('should save config center configs map', () => {
102
+ const configs = new Map([
103
+ ['app', { dataId: 'app-config', groupName: 'DEFAULT_GROUP' }],
104
+ ]);
105
+
106
+ const options: ConfigModuleOptions = {
107
+ defaultConfig: { app: { name: 'test' } },
108
+ configCenter: {
109
+ enabled: true,
110
+ configs,
111
+ },
112
+ };
113
+
114
+ ConfigModule.forRoot(options);
115
+
116
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
117
+ const configProvider = metadata.providers.find(
118
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
119
+ );
120
+ const service = configProvider.useValue as ConfigService;
121
+
122
+ expect((service as any)._configCenterOptions.configs).toBe(configs);
123
+ });
124
+
125
+ test('should save configCenterPriority option', () => {
126
+ const options: ConfigModuleOptions = {
127
+ defaultConfig: {},
128
+ configCenter: {
129
+ enabled: true,
130
+ configCenterPriority: false,
131
+ },
132
+ };
133
+
134
+ ConfigModule.forRoot(options);
135
+
136
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
137
+ const configProvider = metadata.providers.find(
138
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
139
+ );
140
+ const service = configProvider.useValue as ConfigService;
141
+
142
+ expect((service as any)._configCenterOptions.configCenterPriority).toBe(false);
143
+ });
144
+ });
145
+
146
+ describe('ConfigService extended', () => {
147
+ test('should update config via updateConfig', () => {
148
+ const service = new ConfigService({ initial: 'value' });
149
+
150
+ service.updateConfig({ initial: 'updated', newKey: 'newValue' });
151
+
152
+ expect(service.get<string>('initial')).toBe('updated');
153
+ expect(service.get<string>('newKey')).toBe('newValue');
154
+ });
155
+
156
+ test('should get all config', () => {
157
+ const config = { a: 1, b: { c: 2 } };
158
+ const service = new ConfigService(config);
159
+
160
+ const all = service.getAll();
161
+
162
+ expect(all).toEqual(config);
163
+ });
164
+
165
+ test('should work with namespace prefix', () => {
166
+ const service = new ConfigService({
167
+ app: {
168
+ name: 'test',
169
+ settings: {
170
+ debug: true,
171
+ },
172
+ },
173
+ }, 'app');
174
+
175
+ // 带命名空间访问
176
+ expect(service.get<string>('name')).toBe('test');
177
+ expect(service.get<boolean>('settings.debug')).toBe(true);
178
+
179
+ // 完整路径访问也应该工作
180
+ expect(service.get<string>('app.name')).toBe('test');
181
+ });
182
+
183
+ test('should merge config via mergeConfig', () => {
184
+ const service = new ConfigService({ a: 1, b: 2 });
185
+
186
+ service.mergeConfig({ b: 3, c: 4 });
187
+
188
+ expect(service.get<number>('a')).toBe(1);
189
+ expect(service.get<number>('b')).toBe(3);
190
+ expect(service.get<number>('c')).toBe(4);
191
+ });
192
+
193
+ test('should notify listeners on config update', () => {
194
+ const service = new ConfigService({ value: 'initial' });
195
+ let receivedConfig: any = null;
196
+
197
+ service.onConfigUpdate((config) => {
198
+ receivedConfig = config;
199
+ });
200
+
201
+ service.updateConfig({ value: 'updated' });
202
+
203
+ expect(receivedConfig).toEqual({ value: 'updated' });
204
+ });
205
+
206
+ test('should notify listeners on config merge', () => {
207
+ const service = new ConfigService({ a: 1 });
208
+ let receivedConfig: any = null;
209
+
210
+ service.onConfigUpdate((config) => {
211
+ receivedConfig = config;
212
+ });
213
+
214
+ service.mergeConfig({ b: 2 });
215
+
216
+ expect(receivedConfig).toEqual({ a: 1, b: 2 });
217
+ });
218
+
219
+ test('should unsubscribe from config updates', () => {
220
+ const service = new ConfigService({ value: 'initial' });
221
+ let callCount = 0;
222
+
223
+ const unsubscribe = service.onConfigUpdate(() => {
224
+ callCount++;
225
+ });
226
+
227
+ service.updateConfig({ value: 'first' });
228
+ expect(callCount).toBe(1);
229
+
230
+ unsubscribe();
231
+ service.updateConfig({ value: 'second' });
232
+ expect(callCount).toBe(1); // 不应该增加
233
+ });
234
+
235
+ test('should handle listener errors gracefully', () => {
236
+ const service = new ConfigService({ value: 'initial' });
237
+ let errorThrown = false;
238
+
239
+ // 添加一个会抛出错误的监听器
240
+ service.onConfigUpdate(() => {
241
+ throw new Error('Listener error');
242
+ });
243
+
244
+ // 添加一个正常的监听器
245
+ let received = false;
246
+ service.onConfigUpdate(() => {
247
+ received = true;
248
+ });
249
+
250
+ // 捕获 console.error
251
+ const originalError = console.error;
252
+ console.error = () => {
253
+ errorThrown = true;
254
+ };
255
+
256
+ try {
257
+ service.updateConfig({ value: 'updated' });
258
+
259
+ // 错误应该被捕获
260
+ expect(errorThrown).toBe(true);
261
+ // 第二个监听器应该仍然被调用
262
+ expect(received).toBe(true);
263
+ } finally {
264
+ console.error = originalError;
265
+ }
266
+ });
267
+
268
+ test('should get required value', () => {
269
+ const service = new ConfigService({ required: 'exists' });
270
+
271
+ expect(service.getRequired<string>('required')).toBe('exists');
272
+ });
273
+
274
+ test('should throw for missing required value', () => {
275
+ const service = new ConfigService({});
276
+
277
+ expect(() => service.getRequired('missing')).toThrow(
278
+ 'Config value required for key: missing',
279
+ );
280
+ });
281
+
282
+ test('should create namespace view', () => {
283
+ const service = new ConfigService({
284
+ db: { host: 'localhost', port: 5432 },
285
+ });
286
+
287
+ const dbConfig = service.withNamespace('db');
288
+
289
+ expect(dbConfig.get<string>('host')).toBe('localhost');
290
+ expect(dbConfig.get<number>('port')).toBe(5432);
291
+ });
292
+
293
+ test('should handle empty namespace key', () => {
294
+ const service = new ConfigService({
295
+ ns: { a: 1 },
296
+ }, 'ns');
297
+
298
+ // 空键应该返回命名空间下的整个对象
299
+ const result = service.get('');
300
+ expect(result).toEqual({ a: 1 });
301
+ });
302
+ });
303
+
304
+ describe('ConfigModule snapshotEnv', () => {
305
+ test('should load config from environment via load function', () => {
306
+ const originalEnv = process.env.TEST_VAR;
307
+ process.env.TEST_VAR = 'test-value';
308
+
309
+ try {
310
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, ConfigModule);
311
+
312
+ ConfigModule.forRoot({
313
+ defaultConfig: {},
314
+ load: (env) => ({
315
+ testVar: env.TEST_VAR,
316
+ }),
317
+ });
318
+
319
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
320
+ const configProvider = metadata.providers.find(
321
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
322
+ );
323
+ const service = configProvider.useValue as ConfigService;
324
+
325
+ expect(service.get<string>('testVar')).toBe('test-value');
326
+ } finally {
327
+ if (originalEnv === undefined) {
328
+ delete process.env.TEST_VAR;
329
+ } else {
330
+ process.env.TEST_VAR = originalEnv;
331
+ }
332
+ }
333
+ });
334
+ });
@@ -0,0 +1,165 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { ConfigModule } from '../../src/config/config-module';
5
+ import { ConfigService } from '../../src/config/service';
6
+ import { CONFIG_SERVICE_TOKEN } from '../../src/config/types';
7
+ import { MODULE_METADATA_KEY } from '../../src/di/module';
8
+
9
+ describe('ConfigModule', () => {
10
+ beforeEach(() => {
11
+ // Clear metadata before each test
12
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, ConfigModule);
13
+ });
14
+
15
+ describe('forRoot', () => {
16
+ test('should create module with default config', () => {
17
+ ConfigModule.forRoot({
18
+ defaultConfig: { app: { name: 'TestApp', port: 3000 } },
19
+ });
20
+
21
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
22
+ expect(metadata.providers).toBeDefined();
23
+ expect(metadata.providers.length).toBeGreaterThan(0);
24
+ expect(metadata.exports).toContain(CONFIG_SERVICE_TOKEN);
25
+ });
26
+
27
+ test('should merge defaultConfig with loaded config', () => {
28
+ process.env.TEST_PORT = '4000';
29
+
30
+ ConfigModule.forRoot({
31
+ defaultConfig: { app: { name: 'TestApp', port: 3000 } },
32
+ load: (env) => ({
33
+ app: { port: Number(env.TEST_PORT), name: 'LoadedApp' },
34
+ }),
35
+ });
36
+
37
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
38
+ const serviceProvider = metadata.providers.find(
39
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
40
+ );
41
+ const service = serviceProvider?.useValue as ConfigService;
42
+
43
+ // Loaded config should override default (shallow merge at top level)
44
+ expect(service.get('app.port')).toBe(4000);
45
+ // Note: shallow merge means the entire 'app' object is replaced
46
+ expect(service.get('app.name')).toBe('LoadedApp');
47
+
48
+ delete process.env.TEST_PORT;
49
+ });
50
+
51
+ test('should support custom namespace', () => {
52
+ ConfigModule.forRoot({
53
+ defaultConfig: { db: { host: 'localhost' } },
54
+ namespace: 'myapp',
55
+ });
56
+
57
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
58
+ const serviceProvider = metadata.providers.find(
59
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
60
+ );
61
+ const service = serviceProvider?.useValue as ConfigService;
62
+
63
+ // The service wraps config with namespace access
64
+ expect(service.getAll()).toHaveProperty('db');
65
+ });
66
+
67
+ test('should call validate function', () => {
68
+ let validated = false;
69
+ ConfigModule.forRoot({
70
+ defaultConfig: { test: 'value' },
71
+ validate: (config) => {
72
+ validated = true;
73
+ expect(config.test).toBe('value');
74
+ },
75
+ });
76
+
77
+ expect(validated).toBe(true);
78
+ });
79
+
80
+ test('should throw if validation fails', () => {
81
+ expect(() => {
82
+ ConfigModule.forRoot({
83
+ defaultConfig: { test: 'value' },
84
+ validate: () => {
85
+ throw new Error('Validation failed');
86
+ },
87
+ });
88
+ }).toThrow('Validation failed');
89
+ });
90
+
91
+ test('should store configCenter options when enabled', () => {
92
+ ConfigModule.forRoot({
93
+ defaultConfig: {},
94
+ configCenter: {
95
+ enabled: true,
96
+ configs: new Map(),
97
+ },
98
+ });
99
+
100
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
101
+ const serviceProvider = metadata.providers.find(
102
+ (p: any) => p.provide === CONFIG_SERVICE_TOKEN,
103
+ );
104
+ const service = serviceProvider?.useValue as ConfigService;
105
+
106
+ expect((service as any)._configCenterOptions).toBeDefined();
107
+ });
108
+
109
+ test('should work with empty options', () => {
110
+ ConfigModule.forRoot();
111
+
112
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
113
+ expect(metadata.providers).toBeDefined();
114
+ });
115
+
116
+ test('should accumulate providers on multiple calls', () => {
117
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, ConfigModule);
118
+
119
+ ConfigModule.forRoot({ defaultConfig: { a: 1 } });
120
+ const metadata1 = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
121
+ const providersCount1 = metadata1.providers.length;
122
+
123
+ ConfigModule.forRoot({ defaultConfig: { b: 2 } });
124
+ const metadata2 = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule);
125
+ const providersCount2 = metadata2.providers.length;
126
+
127
+ expect(providersCount2).toBeGreaterThan(providersCount1);
128
+ });
129
+ });
130
+ });
131
+
132
+ describe('ConfigModule.setValueByPath', () => {
133
+ // Access private method for testing
134
+ const setValueByPath = (ConfigModule as any).setValueByPath.bind(ConfigModule);
135
+
136
+ test('should set top-level value', () => {
137
+ const obj: Record<string, unknown> = {};
138
+ setValueByPath(obj, 'key', 'value');
139
+ expect(obj.key).toBe('value');
140
+ });
141
+
142
+ test('should set nested value', () => {
143
+ const obj: Record<string, unknown> = {};
144
+ setValueByPath(obj, 'a.b.c', 'deep');
145
+ expect((obj.a as any).b.c).toBe('deep');
146
+ });
147
+
148
+ test('should create intermediate objects', () => {
149
+ const obj: Record<string, unknown> = {};
150
+ setValueByPath(obj, 'level1.level2.level3', 123);
151
+ expect((obj.level1 as any).level2.level3).toBe(123);
152
+ });
153
+
154
+ test('should overwrite existing primitive with object', () => {
155
+ const obj: Record<string, unknown> = { a: 'string' };
156
+ setValueByPath(obj, 'a.b', 'new');
157
+ expect((obj.a as any).b).toBe('new');
158
+ });
159
+
160
+ test('should handle null values in path', () => {
161
+ const obj: Record<string, unknown> = { a: null };
162
+ setValueByPath(obj, 'a.b', 'value');
163
+ expect((obj.a as any).b).toBe('value');
164
+ });
165
+ });