@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,605 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { BaseInterceptor } from '../../src/interceptor/base-interceptor';
5
+ import { Container } from '../../src/di/container';
6
+ import { Context } from '../../src/core/context';
7
+
8
+ // 创建具体的测试拦截器实现
9
+ class TestInterceptor extends BaseInterceptor {
10
+ public beforeCalled = false;
11
+ public afterCalled = false;
12
+ public onErrorCalled = false;
13
+ public lastArgs: unknown[] = [];
14
+ public lastResult: unknown;
15
+ public lastError: unknown;
16
+
17
+ public async execute<T>(
18
+ target: unknown,
19
+ propertyKey: string | symbol,
20
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
21
+ args: unknown[],
22
+ container: Container,
23
+ context?: Context,
24
+ ): Promise<T> {
25
+ await this.before(target, propertyKey, args, container, context);
26
+ this.beforeCalled = true;
27
+ this.lastArgs = args;
28
+
29
+ try {
30
+ const result = await Promise.resolve(originalMethod.apply(target, args));
31
+ const processedResult = await this.after(target, propertyKey, result as T, container, context);
32
+ this.afterCalled = true;
33
+ this.lastResult = processedResult;
34
+ return processedResult;
35
+ } catch (error) {
36
+ this.lastError = error;
37
+ return await this.onError(target, propertyKey, error, container, context);
38
+ }
39
+ }
40
+ }
41
+
42
+ // 自定义前置处理的拦截器
43
+ class BeforeInterceptor extends BaseInterceptor {
44
+ public modifiedArgs: unknown[] = [];
45
+
46
+ protected async before(
47
+ target: unknown,
48
+ propertyKey: string | symbol,
49
+ args: unknown[],
50
+ container: Container,
51
+ context?: Context,
52
+ ): Promise<void> {
53
+ // 记录参数
54
+ this.modifiedArgs = [...args];
55
+ }
56
+
57
+ public async execute<T>(
58
+ target: unknown,
59
+ propertyKey: string | symbol,
60
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
61
+ args: unknown[],
62
+ container: Container,
63
+ context?: Context,
64
+ ): Promise<T> {
65
+ await this.before(target, propertyKey, args, container, context);
66
+ return await Promise.resolve(originalMethod.apply(target, args));
67
+ }
68
+ }
69
+
70
+ // 自定义后置处理的拦截器
71
+ class AfterInterceptor extends BaseInterceptor {
72
+ protected async after<T>(
73
+ target: unknown,
74
+ propertyKey: string | symbol,
75
+ result: T,
76
+ container: Container,
77
+ context?: Context,
78
+ ): Promise<T> {
79
+ // 修改结果
80
+ if (typeof result === 'number') {
81
+ return (result * 2) as T;
82
+ }
83
+ return result;
84
+ }
85
+
86
+ public async execute<T>(
87
+ target: unknown,
88
+ propertyKey: string | symbol,
89
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
90
+ args: unknown[],
91
+ container: Container,
92
+ context?: Context,
93
+ ): Promise<T> {
94
+ const result = await Promise.resolve(originalMethod.apply(target, args));
95
+ return await this.after(target, propertyKey, result as T, container, context);
96
+ }
97
+ }
98
+
99
+ // 自定义错误处理的拦截器
100
+ class ErrorInterceptor extends BaseInterceptor {
101
+ public errorHandled = false;
102
+ public customErrorMessage = 'Custom error';
103
+
104
+ protected async onError(
105
+ target: unknown,
106
+ propertyKey: string | symbol,
107
+ error: unknown,
108
+ container: Container,
109
+ context?: Context,
110
+ ): Promise<never> {
111
+ this.errorHandled = true;
112
+ throw new Error(this.customErrorMessage);
113
+ }
114
+
115
+ public async execute<T>(
116
+ target: unknown,
117
+ propertyKey: string | symbol,
118
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
119
+ args: unknown[],
120
+ container: Container,
121
+ context?: Context,
122
+ ): Promise<T> {
123
+ try {
124
+ return await Promise.resolve(originalMethod.apply(target, args));
125
+ } catch (error) {
126
+ return await this.onError(target, propertyKey, error, container, context);
127
+ }
128
+ }
129
+ }
130
+
131
+ // 测试元数据的拦截器
132
+ const TEST_METADATA_KEY = Symbol('test:metadata');
133
+
134
+ class MetadataInterceptor extends BaseInterceptor {
135
+ public foundMetadata: unknown;
136
+
137
+ public async execute<T>(
138
+ target: unknown,
139
+ propertyKey: string | symbol,
140
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
141
+ args: unknown[],
142
+ container: Container,
143
+ context?: Context,
144
+ ): Promise<T> {
145
+ this.foundMetadata = this.getMetadata(target, propertyKey, TEST_METADATA_KEY);
146
+ return await Promise.resolve(originalMethod.apply(target, args));
147
+ }
148
+ }
149
+
150
+ // 测试上下文辅助方法的拦截器
151
+ class ContextInterceptor extends BaseInterceptor {
152
+ public contextValue: unknown;
153
+ public headerValue: string | null = null;
154
+ public queryValue: string | null = null;
155
+ public paramValue: string | undefined;
156
+
157
+ public async execute<T>(
158
+ target: unknown,
159
+ propertyKey: string | symbol,
160
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
161
+ args: unknown[],
162
+ container: Container,
163
+ context?: Context,
164
+ ): Promise<T> {
165
+ this.contextValue = this.getContextValue(context, 'testKey');
166
+ this.headerValue = this.getHeader(context, 'x-test-header');
167
+ this.queryValue = this.getQuery(context, 'testQuery');
168
+ this.paramValue = this.getParam(context, 'id');
169
+ return await Promise.resolve(originalMethod.apply(target, args));
170
+ }
171
+ }
172
+
173
+ // 测试服务解析的拦截器
174
+ class ServiceInterceptor extends BaseInterceptor {
175
+ public resolvedService: unknown;
176
+
177
+ public async execute<T>(
178
+ target: unknown,
179
+ propertyKey: string | symbol,
180
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
181
+ args: unknown[],
182
+ container: Container,
183
+ context?: Context,
184
+ ): Promise<T> {
185
+ this.resolvedService = this.resolveService(container, 'TestService');
186
+ return await Promise.resolve(originalMethod.apply(target, args));
187
+ }
188
+ }
189
+
190
+ describe('BaseInterceptor', () => {
191
+ let container: Container;
192
+
193
+ beforeEach(() => {
194
+ container = new Container();
195
+ });
196
+
197
+ describe('execute lifecycle', () => {
198
+ test('should call before, execute method, and after', async () => {
199
+ const interceptor = new TestInterceptor();
200
+ const target = {
201
+ method: (x: number) => x * 2,
202
+ };
203
+
204
+ const result = await interceptor.execute(
205
+ target,
206
+ 'method',
207
+ target.method.bind(target),
208
+ [5],
209
+ container,
210
+ );
211
+
212
+ expect(result).toBe(10);
213
+ expect(interceptor.beforeCalled).toBe(true);
214
+ expect(interceptor.afterCalled).toBe(true);
215
+ expect(interceptor.lastArgs).toEqual([5]);
216
+ expect(interceptor.lastResult).toBe(10);
217
+ });
218
+
219
+ test('should handle errors and call onError', async () => {
220
+ const interceptor = new TestInterceptor();
221
+ const target = {
222
+ method: () => {
223
+ throw new Error('Test error');
224
+ },
225
+ };
226
+
227
+ await expect(
228
+ interceptor.execute(
229
+ target,
230
+ 'method',
231
+ target.method.bind(target),
232
+ [],
233
+ container,
234
+ ),
235
+ ).rejects.toThrow('Test error');
236
+
237
+ expect(interceptor.beforeCalled).toBe(true);
238
+ expect(interceptor.lastError).toBeInstanceOf(Error);
239
+ });
240
+ });
241
+
242
+ describe('before hook', () => {
243
+ test('should allow custom before processing', async () => {
244
+ const interceptor = new BeforeInterceptor();
245
+ const target = {
246
+ method: (a: number, b: string) => `${a}-${b}`,
247
+ };
248
+
249
+ await interceptor.execute(
250
+ target,
251
+ 'method',
252
+ target.method.bind(target),
253
+ [42, 'test'],
254
+ container,
255
+ );
256
+
257
+ expect(interceptor.modifiedArgs).toEqual([42, 'test']);
258
+ });
259
+ });
260
+
261
+ describe('after hook', () => {
262
+ test('should allow result transformation', async () => {
263
+ const interceptor = new AfterInterceptor();
264
+ const target = {
265
+ method: () => 21,
266
+ };
267
+
268
+ const result = await interceptor.execute(
269
+ target,
270
+ 'method',
271
+ target.method.bind(target),
272
+ [],
273
+ container,
274
+ );
275
+
276
+ expect(result).toBe(42); // 21 * 2
277
+ });
278
+
279
+ test('should return original result for non-number types', async () => {
280
+ const interceptor = new AfterInterceptor();
281
+ const target = {
282
+ method: () => 'hello',
283
+ };
284
+
285
+ const result = await interceptor.execute(
286
+ target,
287
+ 'method',
288
+ target.method.bind(target),
289
+ [],
290
+ container,
291
+ );
292
+
293
+ expect(result).toBe('hello');
294
+ });
295
+ });
296
+
297
+ describe('onError hook', () => {
298
+ test('should allow custom error handling', async () => {
299
+ const interceptor = new ErrorInterceptor();
300
+ const target = {
301
+ method: () => {
302
+ throw new Error('Original error');
303
+ },
304
+ };
305
+
306
+ await expect(
307
+ interceptor.execute(
308
+ target,
309
+ 'method',
310
+ target.method.bind(target),
311
+ [],
312
+ container,
313
+ ),
314
+ ).rejects.toThrow('Custom error');
315
+
316
+ expect(interceptor.errorHandled).toBe(true);
317
+ });
318
+ });
319
+
320
+ describe('getMetadata', () => {
321
+ test('should get metadata from prototype', async () => {
322
+ class TestClass {
323
+ public testMethod(): string {
324
+ return 'test';
325
+ }
326
+ }
327
+
328
+ Reflect.defineMetadata(TEST_METADATA_KEY, { value: 'metadata' }, TestClass.prototype, 'testMethod');
329
+
330
+ const interceptor = new MetadataInterceptor();
331
+ const instance = new TestClass();
332
+
333
+ await interceptor.execute(
334
+ instance,
335
+ 'testMethod',
336
+ instance.testMethod.bind(instance),
337
+ [],
338
+ container,
339
+ );
340
+
341
+ expect(interceptor.foundMetadata).toEqual({ value: 'metadata' });
342
+ });
343
+
344
+ test('should get metadata from instance when stored on instance', async () => {
345
+ const instance = {
346
+ testMethod: () => 'test',
347
+ };
348
+
349
+ Reflect.defineMetadata(TEST_METADATA_KEY, { value: 'instance-metadata' }, instance, 'testMethod');
350
+
351
+ const interceptor = new MetadataInterceptor();
352
+
353
+ await interceptor.execute(
354
+ instance,
355
+ 'testMethod',
356
+ instance.testMethod.bind(instance),
357
+ [],
358
+ container,
359
+ );
360
+
361
+ expect(interceptor.foundMetadata).toEqual({ value: 'instance-metadata' });
362
+ });
363
+
364
+ test('should return undefined for non-object target', async () => {
365
+ const interceptor = new MetadataInterceptor();
366
+
367
+ await interceptor.execute(
368
+ null,
369
+ 'method',
370
+ () => 'test',
371
+ [],
372
+ container,
373
+ );
374
+
375
+ expect(interceptor.foundMetadata).toBeUndefined();
376
+ });
377
+
378
+ test('should return undefined when metadata does not exist', async () => {
379
+ class TestClass {
380
+ public noMetadataMethod(): string {
381
+ return 'test';
382
+ }
383
+ }
384
+
385
+ const interceptor = new MetadataInterceptor();
386
+ const instance = new TestClass();
387
+
388
+ await interceptor.execute(
389
+ instance,
390
+ 'noMetadataMethod',
391
+ instance.noMetadataMethod.bind(instance),
392
+ [],
393
+ container,
394
+ );
395
+
396
+ expect(interceptor.foundMetadata).toBeUndefined();
397
+ });
398
+
399
+ test('should get metadata from constructor prototype as fallback', async () => {
400
+ class TestClass {
401
+ public testMethod(): string {
402
+ return 'test';
403
+ }
404
+ }
405
+
406
+ // 在构造函数原型上设置元数据
407
+ Reflect.defineMetadata(TEST_METADATA_KEY, { value: 'constructor-metadata' }, TestClass.prototype, 'testMethod');
408
+
409
+ const interceptor = new MetadataInterceptor();
410
+ const instance = new TestClass();
411
+
412
+ await interceptor.execute(
413
+ instance,
414
+ 'testMethod',
415
+ instance.testMethod.bind(instance),
416
+ [],
417
+ container,
418
+ );
419
+
420
+ expect(interceptor.foundMetadata).toEqual({ value: 'constructor-metadata' });
421
+ });
422
+ });
423
+
424
+ describe('resolveService', () => {
425
+ test('should resolve service from container using registerInstance', async () => {
426
+ const TEST_SERVICE_TOKEN = Symbol('TestService');
427
+ const testServiceInstance = { name: 'TestService' };
428
+
429
+ // 使用 registerInstance 注册实例
430
+ container.registerInstance(TEST_SERVICE_TOKEN, testServiceInstance);
431
+
432
+ // 使用内联拦截器
433
+ class LocalServiceInterceptor extends BaseInterceptor {
434
+ public resolvedService: unknown;
435
+
436
+ public async execute<T>(
437
+ target: unknown,
438
+ propertyKey: string | symbol,
439
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
440
+ args: unknown[],
441
+ container: Container,
442
+ context?: Context,
443
+ ): Promise<T> {
444
+ this.resolvedService = this.resolveService(container, TEST_SERVICE_TOKEN);
445
+ return await Promise.resolve(originalMethod.apply(target, args));
446
+ }
447
+ }
448
+
449
+ const interceptor = new LocalServiceInterceptor();
450
+
451
+ await interceptor.execute(
452
+ {},
453
+ 'method',
454
+ () => 'test',
455
+ [],
456
+ container,
457
+ );
458
+
459
+ expect(interceptor.resolvedService).toBe(testServiceInstance);
460
+ expect((interceptor.resolvedService as { name: string }).name).toBe('TestService');
461
+ });
462
+ });
463
+
464
+ describe('context helpers', () => {
465
+ test('should return undefined/null when context is undefined', async () => {
466
+ const interceptor = new ContextInterceptor();
467
+
468
+ await interceptor.execute(
469
+ {},
470
+ 'method',
471
+ () => 'test',
472
+ [],
473
+ container,
474
+ undefined,
475
+ );
476
+
477
+ expect(interceptor.contextValue).toBeUndefined();
478
+ expect(interceptor.headerValue).toBeNull();
479
+ expect(interceptor.queryValue).toBeNull();
480
+ expect(interceptor.paramValue).toBeUndefined();
481
+ });
482
+
483
+ test('should get header from context', async () => {
484
+ const request = new Request('http://localhost/test', {
485
+ headers: {
486
+ 'x-test-header': 'test-value',
487
+ },
488
+ });
489
+ const context = new Context(request);
490
+
491
+ const interceptor = new ContextInterceptor();
492
+
493
+ await interceptor.execute(
494
+ {},
495
+ 'method',
496
+ () => 'test',
497
+ [],
498
+ container,
499
+ context,
500
+ );
501
+
502
+ expect(interceptor.headerValue).toBe('test-value');
503
+ });
504
+
505
+ test('should get query from context', async () => {
506
+ const request = new Request('http://localhost/test?testQuery=queryValue');
507
+ const context = new Context(request);
508
+
509
+ const interceptor = new ContextInterceptor();
510
+
511
+ await interceptor.execute(
512
+ {},
513
+ 'method',
514
+ () => 'test',
515
+ [],
516
+ container,
517
+ context,
518
+ );
519
+
520
+ expect(interceptor.queryValue).toBe('queryValue');
521
+ });
522
+
523
+ test('should get param from context', async () => {
524
+ const request = new Request('http://localhost/users/123');
525
+ const context = new Context(request);
526
+ // 直接设置 params 属性
527
+ context.params = { id: '123' };
528
+
529
+ const interceptor = new ContextInterceptor();
530
+
531
+ await interceptor.execute(
532
+ {},
533
+ 'method',
534
+ () => 'test',
535
+ [],
536
+ container,
537
+ context,
538
+ );
539
+
540
+ expect(interceptor.paramValue).toBe('123');
541
+ });
542
+
543
+ test('should return undefined for getContextValue (base implementation)', async () => {
544
+ const request = new Request('http://localhost/test');
545
+ const context = new Context(request);
546
+
547
+ const interceptor = new ContextInterceptor();
548
+
549
+ await interceptor.execute(
550
+ {},
551
+ 'method',
552
+ () => 'test',
553
+ [],
554
+ container,
555
+ context,
556
+ );
557
+
558
+ // getContextValue 的基础实现总是返回 undefined
559
+ expect(interceptor.contextValue).toBeUndefined();
560
+ });
561
+ });
562
+
563
+ describe('async method handling', () => {
564
+ test('should handle async original method', async () => {
565
+ const interceptor = new TestInterceptor();
566
+ const target = {
567
+ asyncMethod: async (x: number) => {
568
+ await new Promise((resolve) => setTimeout(resolve, 10));
569
+ return x * 3;
570
+ },
571
+ };
572
+
573
+ const result = await interceptor.execute(
574
+ target,
575
+ 'asyncMethod',
576
+ target.asyncMethod.bind(target),
577
+ [7],
578
+ container,
579
+ );
580
+
581
+ expect(result).toBe(21);
582
+ expect(interceptor.afterCalled).toBe(true);
583
+ });
584
+
585
+ test('should handle async method that throws', async () => {
586
+ const interceptor = new TestInterceptor();
587
+ const target = {
588
+ asyncMethod: async () => {
589
+ await new Promise((resolve) => setTimeout(resolve, 10));
590
+ throw new Error('Async error');
591
+ },
592
+ };
593
+
594
+ await expect(
595
+ interceptor.execute(
596
+ target,
597
+ 'asyncMethod',
598
+ target.asyncMethod.bind(target),
599
+ [],
600
+ container,
601
+ ),
602
+ ).rejects.toThrow('Async error');
603
+ });
604
+ });
605
+ });