@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,147 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import {
5
+ ServiceRegistry,
6
+ getServiceRegistryMetadata,
7
+ type ServiceRegistryMetadata,
8
+ } from '../../src/microservice/service-registry/decorators';
9
+
10
+ describe('ServiceRegistry Decorator', () => {
11
+ test('should set metadata with service name only', () => {
12
+ @ServiceRegistry('my-service')
13
+ class TestController {}
14
+
15
+ const metadata = getServiceRegistryMetadata(TestController);
16
+ expect(metadata).toBeDefined();
17
+ expect(metadata?.serviceName).toBe('my-service');
18
+ expect(metadata?.healthy).toBe(true);
19
+ });
20
+
21
+ test('should set metadata with custom ip and port', () => {
22
+ @ServiceRegistry('my-service', { ip: '192.168.1.100', port: 8080 })
23
+ class TestController {}
24
+
25
+ const metadata = getServiceRegistryMetadata(TestController);
26
+ expect(metadata?.ip).toBe('192.168.1.100');
27
+ expect(metadata?.port).toBe(8080);
28
+ });
29
+
30
+ test('should set metadata with weight', () => {
31
+ @ServiceRegistry('my-service', { weight: 10 })
32
+ class TestController {}
33
+
34
+ const metadata = getServiceRegistryMetadata(TestController);
35
+ expect(metadata?.weight).toBe(10);
36
+ });
37
+
38
+ test('should set metadata with enabled flag', () => {
39
+ @ServiceRegistry('my-service', { enabled: false })
40
+ class TestController {}
41
+
42
+ const metadata = getServiceRegistryMetadata(TestController);
43
+ expect(metadata?.enabled).toBe(false);
44
+ });
45
+
46
+ test('should set metadata with custom metadata', () => {
47
+ @ServiceRegistry('my-service', {
48
+ metadata: { version: '1.0.0', region: 'us-east' },
49
+ })
50
+ class TestController {}
51
+
52
+ const metadata = getServiceRegistryMetadata(TestController);
53
+ expect(metadata?.metadata).toEqual({ version: '1.0.0', region: 'us-east' });
54
+ });
55
+
56
+ test('should set metadata with cluster name', () => {
57
+ @ServiceRegistry('my-service', { clusterName: 'production' })
58
+ class TestController {}
59
+
60
+ const metadata = getServiceRegistryMetadata(TestController);
61
+ expect(metadata?.clusterName).toBe('production');
62
+ });
63
+
64
+ test('should set metadata with namespace id', () => {
65
+ @ServiceRegistry('my-service', { namespaceId: 'dev-namespace' })
66
+ class TestController {}
67
+
68
+ const metadata = getServiceRegistryMetadata(TestController);
69
+ expect(metadata?.namespaceId).toBe('dev-namespace');
70
+ });
71
+
72
+ test('should set metadata with group name', () => {
73
+ @ServiceRegistry('my-service', { groupName: 'DEFAULT_GROUP' })
74
+ class TestController {}
75
+
76
+ const metadata = getServiceRegistryMetadata(TestController);
77
+ expect(metadata?.groupName).toBe('DEFAULT_GROUP');
78
+ });
79
+
80
+ test('should set metadata with healthy flag', () => {
81
+ @ServiceRegistry('my-service', { healthy: false })
82
+ class TestController {}
83
+
84
+ const metadata = getServiceRegistryMetadata(TestController);
85
+ expect(metadata?.healthy).toBe(false);
86
+ });
87
+
88
+ test('should set all options together', () => {
89
+ @ServiceRegistry('full-service', {
90
+ ip: '10.0.0.1',
91
+ port: 9000,
92
+ weight: 5,
93
+ enabled: true,
94
+ metadata: { env: 'production' },
95
+ clusterName: 'cluster-a',
96
+ namespaceId: 'ns-prod',
97
+ groupName: 'app-group',
98
+ healthy: true,
99
+ })
100
+ class TestController {}
101
+
102
+ const metadata = getServiceRegistryMetadata(TestController);
103
+ expect(metadata?.serviceName).toBe('full-service');
104
+ expect(metadata?.ip).toBe('10.0.0.1');
105
+ expect(metadata?.port).toBe(9000);
106
+ expect(metadata?.weight).toBe(5);
107
+ expect(metadata?.enabled).toBe(true);
108
+ expect(metadata?.metadata).toEqual({ env: 'production' });
109
+ expect(metadata?.clusterName).toBe('cluster-a');
110
+ expect(metadata?.namespaceId).toBe('ns-prod');
111
+ expect(metadata?.groupName).toBe('app-group');
112
+ expect(metadata?.healthy).toBe(true);
113
+ });
114
+ });
115
+
116
+ describe('getServiceRegistryMetadata', () => {
117
+ test('should return undefined for non-decorated class', () => {
118
+ class PlainClass {}
119
+
120
+ const metadata = getServiceRegistryMetadata(PlainClass);
121
+ expect(metadata).toBeUndefined();
122
+ });
123
+
124
+ test('should return metadata for decorated class', () => {
125
+ @ServiceRegistry('test-service')
126
+ class DecoratedClass {}
127
+
128
+ const metadata = getServiceRegistryMetadata(DecoratedClass);
129
+ expect(metadata).toBeDefined();
130
+ expect(metadata?.serviceName).toBe('test-service');
131
+ });
132
+
133
+ test('should keep separate metadata for different classes', () => {
134
+ @ServiceRegistry('service-a')
135
+ class ServiceA {}
136
+
137
+ @ServiceRegistry('service-b', { port: 3001 })
138
+ class ServiceB {}
139
+
140
+ const metadataA = getServiceRegistryMetadata(ServiceA);
141
+ const metadataB = getServiceRegistryMetadata(ServiceB);
142
+
143
+ expect(metadataA?.serviceName).toBe('service-a');
144
+ expect(metadataB?.serviceName).toBe('service-b');
145
+ expect(metadataB?.port).toBe(3001);
146
+ });
147
+ });
@@ -0,0 +1,213 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+
3
+ import { Tracer } from '../../src/microservice/tracing/tracer';
4
+ import { SpanKind, SpanStatus, type Span, type TraceCollector } from '../../src/microservice/tracing/types';
5
+
6
+ describe('Tracer', () => {
7
+ let tracer: Tracer;
8
+
9
+ beforeEach(() => {
10
+ tracer = new Tracer();
11
+ });
12
+
13
+ describe('constructor', () => {
14
+ test('should create tracer with default options', () => {
15
+ const t = new Tracer();
16
+ const span = t.startSpan('test');
17
+ expect(span).toBeDefined();
18
+ });
19
+
20
+ test('should create tracer with custom options', () => {
21
+ const t = new Tracer({
22
+ enabled: true,
23
+ samplingRate: 0.5,
24
+ });
25
+ const span = t.startSpan('test');
26
+ expect(span).toBeDefined();
27
+ });
28
+
29
+ test('should create empty span when disabled', () => {
30
+ const t = new Tracer({ enabled: false });
31
+ const span = t.startSpan('test');
32
+ expect(span.name).toBe('test');
33
+ expect(span.context.sampled).toBe(false);
34
+ });
35
+ });
36
+
37
+ describe('startSpan', () => {
38
+ test('should start a new span', () => {
39
+ const span = tracer.startSpan('test-operation');
40
+ expect(span.name).toBe('test-operation');
41
+ expect(span.kind).toBe(SpanKind.INTERNAL);
42
+ expect(span.context.traceId).toBeDefined();
43
+ expect(span.context.spanId).toBeDefined();
44
+ expect(span.startTime).toBeDefined();
45
+ expect(span.status).toBe(SpanStatus.UNSET);
46
+ });
47
+
48
+ test('should start span with custom kind', () => {
49
+ const span = tracer.startSpan('http-request', SpanKind.CLIENT);
50
+ expect(span.kind).toBe(SpanKind.CLIENT);
51
+ });
52
+
53
+ test('should start span with parent context', () => {
54
+ const parentSpan = tracer.startSpan('parent');
55
+ const childSpan = tracer.startSpan('child', SpanKind.INTERNAL, parentSpan.context);
56
+
57
+ expect(childSpan.context.traceId).toBe(parentSpan.context.traceId);
58
+ expect(childSpan.context.parentSpanId).toBe(parentSpan.context.spanId);
59
+ });
60
+
61
+ test('should use custom trace id generator', () => {
62
+ const customTracer = new Tracer({
63
+ traceIdGenerator: () => 'custom-trace-id-12345678901234',
64
+ });
65
+ const span = customTracer.startSpan('test');
66
+ expect(span.context.traceId).toBe('custom-trace-id-12345678901234');
67
+ });
68
+
69
+ test('should use custom span id generator', () => {
70
+ const customTracer = new Tracer({
71
+ spanIdGenerator: () => 'custom-span-id',
72
+ });
73
+ const span = customTracer.startSpan('test');
74
+ expect(span.context.spanId).toBe('custom-span-id');
75
+ });
76
+ });
77
+
78
+ describe('endSpan', () => {
79
+ test('should end span with OK status', () => {
80
+ const span = tracer.startSpan('test');
81
+ tracer.endSpan(span.context.spanId);
82
+
83
+ // Span should be ended (removed from active spans)
84
+ // We can verify by checking span properties were set
85
+ expect(span.endTime).toBeDefined();
86
+ expect(span.duration).toBeDefined();
87
+ expect(span.status).toBe(SpanStatus.OK);
88
+ });
89
+
90
+ test('should end span with error status', () => {
91
+ const span = tracer.startSpan('test');
92
+ const error = new Error('Test error');
93
+ tracer.endSpan(span.context.spanId, SpanStatus.ERROR, error);
94
+
95
+ expect(span.status).toBe(SpanStatus.ERROR);
96
+ expect(span.error).toBe(error);
97
+ });
98
+
99
+ test('should not throw for unknown span id', () => {
100
+ expect(() => tracer.endSpan('unknown-span-id')).not.toThrow();
101
+ });
102
+ });
103
+
104
+ describe('addCollector', () => {
105
+ test('should collect span data', () => {
106
+ const collectedSpans: Span[] = [];
107
+ const collector: TraceCollector = {
108
+ collect: (span: Span) => {
109
+ collectedSpans.push(span);
110
+ },
111
+ };
112
+
113
+ tracer.addCollector(collector);
114
+ const span = tracer.startSpan('test');
115
+ tracer.endSpan(span.context.spanId);
116
+
117
+ // Span should be collected if sampled
118
+ expect(collectedSpans.length).toBeGreaterThanOrEqual(0);
119
+ });
120
+
121
+ test('should handle collector errors gracefully', () => {
122
+ const failingCollector: TraceCollector = {
123
+ collect: () => {
124
+ throw new Error('Collector failed');
125
+ },
126
+ };
127
+
128
+ tracer.addCollector(failingCollector);
129
+ const span = tracer.startSpan('test');
130
+
131
+ // Should not throw even if collector fails
132
+ expect(() => tracer.endSpan(span.context.spanId)).not.toThrow();
133
+ });
134
+ });
135
+
136
+ describe('setSpanTag', () => {
137
+ test('should set string tag', () => {
138
+ const span = tracer.startSpan('test');
139
+ tracer.setSpanTag(span.context.spanId, 'http.method', 'GET');
140
+
141
+ expect(span.tags?.['http.method']).toBe('GET');
142
+ });
143
+
144
+ test('should set number tag', () => {
145
+ const span = tracer.startSpan('test');
146
+ tracer.setSpanTag(span.context.spanId, 'http.status_code', 200);
147
+
148
+ expect(span.tags?.['http.status_code']).toBe(200);
149
+ });
150
+
151
+ test('should set boolean tag', () => {
152
+ const span = tracer.startSpan('test');
153
+ tracer.setSpanTag(span.context.spanId, 'error', true);
154
+
155
+ expect(span.tags?.['error']).toBe(true);
156
+ });
157
+
158
+ test('should not throw for unknown span id', () => {
159
+ expect(() => tracer.setSpanTag('unknown', 'key', 'value')).not.toThrow();
160
+ });
161
+ });
162
+
163
+ describe('addSpanEvent', () => {
164
+ test('should not throw when adding event', () => {
165
+ const span = tracer.startSpan('test');
166
+ expect(() => tracer.addSpanEvent(span.context.spanId, 'event-name', { key: 'value' })).not.toThrow();
167
+ });
168
+
169
+ test('should not throw when adding multiple events', () => {
170
+ const span = tracer.startSpan('test');
171
+ expect(() => tracer.addSpanEvent(span.context.spanId, 'event1')).not.toThrow();
172
+ expect(() => tracer.addSpanEvent(span.context.spanId, 'event2')).not.toThrow();
173
+ });
174
+
175
+ test('should not throw for unknown span id', () => {
176
+ expect(() => tracer.addSpanEvent('unknown', 'event')).not.toThrow();
177
+ });
178
+ });
179
+
180
+ describe('sampling', () => {
181
+ test('should respect sampling rate', () => {
182
+ // Create tracer with 0% sampling rate
183
+ const lowSamplingTracer = new Tracer({ samplingRate: 0 });
184
+ const span = lowSamplingTracer.startSpan('test');
185
+ expect(span.context.sampled).toBe(false);
186
+ });
187
+
188
+ test('should sample all with 100% rate', () => {
189
+ const fullSamplingTracer = new Tracer({ samplingRate: 1.0 });
190
+ const span = fullSamplingTracer.startSpan('test');
191
+ // Note: sampled could still be true depending on implementation
192
+ expect(span.context).toBeDefined();
193
+ });
194
+ });
195
+
196
+ describe('parent-child relationship', () => {
197
+ test('should add child to parent span', () => {
198
+ const parentSpan = tracer.startSpan('parent');
199
+ tracer.startSpan('child', SpanKind.INTERNAL, parentSpan.context);
200
+
201
+ // Parent should have the child in children array
202
+ expect(parentSpan.children?.length).toBe(1);
203
+ });
204
+
205
+ test('should handle multiple children', () => {
206
+ const parentSpan = tracer.startSpan('parent');
207
+ tracer.startSpan('child1', SpanKind.INTERNAL, parentSpan.context);
208
+ tracer.startSpan('child2', SpanKind.INTERNAL, parentSpan.context);
209
+
210
+ expect(parentSpan.children?.length).toBe(2);
211
+ });
212
+ });
213
+ });
@@ -0,0 +1,168 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+
3
+ import {
4
+ ConsoleTraceCollector,
5
+ MemoryTraceCollector,
6
+ } from '../../src/microservice/tracing/collectors';
7
+ import { SpanKind, SpanStatus, type Span } from '../../src/microservice/tracing/types';
8
+
9
+ describe('ConsoleTraceCollector', () => {
10
+ test('should collect span to console', () => {
11
+ const collector = new ConsoleTraceCollector();
12
+ const span: Span = {
13
+ context: {
14
+ traceId: 'trace-123',
15
+ spanId: 'span-456',
16
+ sampled: true,
17
+ },
18
+ name: 'test-span',
19
+ kind: SpanKind.INTERNAL,
20
+ startTime: Date.now(),
21
+ status: SpanStatus.OK,
22
+ duration: 100,
23
+ };
24
+
25
+ // Should not throw
26
+ expect(() => collector.collect(span)).not.toThrow();
27
+ });
28
+
29
+ test('should handle span with tags', () => {
30
+ const collector = new ConsoleTraceCollector();
31
+ const span: Span = {
32
+ context: {
33
+ traceId: 'trace-123',
34
+ spanId: 'span-456',
35
+ sampled: true,
36
+ },
37
+ name: 'tagged-span',
38
+ kind: SpanKind.CLIENT,
39
+ startTime: Date.now(),
40
+ status: SpanStatus.OK,
41
+ tags: {
42
+ 'http.method': 'GET',
43
+ 'http.url': '/api/test',
44
+ },
45
+ };
46
+
47
+ expect(() => collector.collect(span)).not.toThrow();
48
+ });
49
+ });
50
+
51
+ describe('MemoryTraceCollector', () => {
52
+ let collector: MemoryTraceCollector;
53
+
54
+ beforeEach(() => {
55
+ collector = new MemoryTraceCollector();
56
+ });
57
+
58
+ describe('collect', () => {
59
+ test('should store span in memory', () => {
60
+ const span: Span = {
61
+ context: {
62
+ traceId: 'trace-1',
63
+ spanId: 'span-1',
64
+ sampled: true,
65
+ },
66
+ name: 'test',
67
+ kind: SpanKind.INTERNAL,
68
+ startTime: Date.now(),
69
+ status: SpanStatus.OK,
70
+ };
71
+
72
+ collector.collect(span);
73
+
74
+ const spans = collector.getSpans();
75
+ expect(spans.length).toBe(1);
76
+ expect(spans[0].name).toBe('test');
77
+ });
78
+
79
+ test('should store multiple spans', () => {
80
+ for (let i = 0; i < 5; i++) {
81
+ collector.collect({
82
+ context: { traceId: `trace-${i}`, spanId: `span-${i}`, sampled: true },
83
+ name: `span-${i}`,
84
+ kind: SpanKind.INTERNAL,
85
+ startTime: Date.now(),
86
+ status: SpanStatus.OK,
87
+ });
88
+ }
89
+
90
+ expect(collector.getSpans().length).toBe(5);
91
+ });
92
+ });
93
+
94
+ describe('getSpans', () => {
95
+ test('should return copy of spans', () => {
96
+ const span: Span = {
97
+ context: { traceId: 't1', spanId: 's1', sampled: true },
98
+ name: 'test',
99
+ kind: SpanKind.INTERNAL,
100
+ startTime: Date.now(),
101
+ status: SpanStatus.OK,
102
+ };
103
+
104
+ collector.collect(span);
105
+
106
+ const spans1 = collector.getSpans();
107
+ const spans2 = collector.getSpans();
108
+
109
+ // Should be different arrays
110
+ expect(spans1).not.toBe(spans2);
111
+ expect(spans1).toEqual(spans2);
112
+ });
113
+ });
114
+
115
+ describe('clear', () => {
116
+ test('should remove all spans', () => {
117
+ collector.collect({
118
+ context: { traceId: 't1', spanId: 's1', sampled: true },
119
+ name: 'test',
120
+ kind: SpanKind.INTERNAL,
121
+ startTime: Date.now(),
122
+ status: SpanStatus.OK,
123
+ });
124
+
125
+ expect(collector.getSpans().length).toBe(1);
126
+
127
+ collector.clear();
128
+
129
+ expect(collector.getSpans().length).toBe(0);
130
+ });
131
+ });
132
+
133
+ describe('getSpansByTraceId', () => {
134
+ test('should filter spans by trace id', () => {
135
+ collector.collect({
136
+ context: { traceId: 'trace-A', spanId: 's1', sampled: true },
137
+ name: 'span-a1',
138
+ kind: SpanKind.INTERNAL,
139
+ startTime: Date.now(),
140
+ status: SpanStatus.OK,
141
+ });
142
+ collector.collect({
143
+ context: { traceId: 'trace-B', spanId: 's2', sampled: true },
144
+ name: 'span-b1',
145
+ kind: SpanKind.INTERNAL,
146
+ startTime: Date.now(),
147
+ status: SpanStatus.OK,
148
+ });
149
+ collector.collect({
150
+ context: { traceId: 'trace-A', spanId: 's3', sampled: true },
151
+ name: 'span-a2',
152
+ kind: SpanKind.INTERNAL,
153
+ startTime: Date.now(),
154
+ status: SpanStatus.OK,
155
+ });
156
+
157
+ const traceASpans = collector.getSpansByTraceId('trace-A');
158
+
159
+ expect(traceASpans.length).toBe(2);
160
+ expect(traceASpans.every((s) => s.context.traceId === 'trace-A')).toBe(true);
161
+ });
162
+
163
+ test('should return empty array for non-existent trace id', () => {
164
+ const spans = collector.getSpansByTraceId('non-existent');
165
+ expect(spans).toEqual([]);
166
+ });
167
+ });
168
+ });