@dangao/bun-server 1.7.1 → 1.8.0

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 (116) hide show
  1. package/README.md +129 -21
  2. package/dist/di/decorators.d.ts +37 -0
  3. package/dist/di/decorators.d.ts.map +1 -1
  4. package/dist/di/index.d.ts +1 -1
  5. package/dist/di/index.d.ts.map +1 -1
  6. package/dist/di/module-registry.d.ts +17 -0
  7. package/dist/di/module-registry.d.ts.map +1 -1
  8. package/dist/events/decorators.d.ts +52 -0
  9. package/dist/events/decorators.d.ts.map +1 -0
  10. package/dist/events/event-module.d.ts +97 -0
  11. package/dist/events/event-module.d.ts.map +1 -0
  12. package/dist/events/index.d.ts +5 -0
  13. package/dist/events/index.d.ts.map +1 -0
  14. package/dist/events/service.d.ts +76 -0
  15. package/dist/events/service.d.ts.map +1 -0
  16. package/dist/events/types.d.ts +184 -0
  17. package/dist/events/types.d.ts.map +1 -0
  18. package/dist/index.d.ts +5 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1511 -11
  21. package/dist/security/filter.d.ts +23 -0
  22. package/dist/security/filter.d.ts.map +1 -1
  23. package/dist/security/guards/builtin/auth-guard.d.ts +44 -0
  24. package/dist/security/guards/builtin/auth-guard.d.ts.map +1 -0
  25. package/dist/security/guards/builtin/index.d.ts +3 -0
  26. package/dist/security/guards/builtin/index.d.ts.map +1 -0
  27. package/dist/security/guards/builtin/roles-guard.d.ts +66 -0
  28. package/dist/security/guards/builtin/roles-guard.d.ts.map +1 -0
  29. package/dist/security/guards/decorators.d.ts +50 -0
  30. package/dist/security/guards/decorators.d.ts.map +1 -0
  31. package/dist/security/guards/execution-context.d.ts +56 -0
  32. package/dist/security/guards/execution-context.d.ts.map +1 -0
  33. package/dist/security/guards/guard-registry.d.ts +67 -0
  34. package/dist/security/guards/guard-registry.d.ts.map +1 -0
  35. package/dist/security/guards/index.d.ts +7 -0
  36. package/dist/security/guards/index.d.ts.map +1 -0
  37. package/dist/security/guards/reflector.d.ts +57 -0
  38. package/dist/security/guards/reflector.d.ts.map +1 -0
  39. package/dist/security/guards/types.d.ts +126 -0
  40. package/dist/security/guards/types.d.ts.map +1 -0
  41. package/dist/security/index.d.ts +1 -0
  42. package/dist/security/index.d.ts.map +1 -1
  43. package/dist/security/security-module.d.ts +20 -0
  44. package/dist/security/security-module.d.ts.map +1 -1
  45. package/dist/validation/class-validator.d.ts +108 -0
  46. package/dist/validation/class-validator.d.ts.map +1 -0
  47. package/dist/validation/custom-validator.d.ts +130 -0
  48. package/dist/validation/custom-validator.d.ts.map +1 -0
  49. package/dist/validation/errors.d.ts +22 -2
  50. package/dist/validation/errors.d.ts.map +1 -1
  51. package/dist/validation/index.d.ts +7 -1
  52. package/dist/validation/index.d.ts.map +1 -1
  53. package/dist/validation/rules/array.d.ts +33 -0
  54. package/dist/validation/rules/array.d.ts.map +1 -0
  55. package/dist/validation/rules/common.d.ts +90 -0
  56. package/dist/validation/rules/common.d.ts.map +1 -0
  57. package/dist/validation/rules/conditional.d.ts +30 -0
  58. package/dist/validation/rules/conditional.d.ts.map +1 -0
  59. package/dist/validation/rules/index.d.ts +5 -0
  60. package/dist/validation/rules/index.d.ts.map +1 -0
  61. package/dist/validation/rules/object.d.ts +30 -0
  62. package/dist/validation/rules/object.d.ts.map +1 -0
  63. package/dist/validation/types.d.ts +52 -1
  64. package/dist/validation/types.d.ts.map +1 -1
  65. package/docs/events.md +494 -0
  66. package/docs/guards.md +376 -0
  67. package/docs/guide.md +309 -1
  68. package/docs/request-lifecycle.md +444 -0
  69. package/docs/validation.md +407 -0
  70. package/docs/zh/events.md +494 -0
  71. package/docs/zh/guards.md +376 -0
  72. package/docs/zh/guide.md +309 -1
  73. package/docs/zh/request-lifecycle.md +444 -0
  74. package/docs/zh/validation.md +407 -0
  75. package/package.json +1 -1
  76. package/src/di/decorators.ts +46 -0
  77. package/src/di/index.ts +10 -1
  78. package/src/di/module-registry.ts +39 -0
  79. package/src/events/decorators.ts +103 -0
  80. package/src/events/event-module.ts +272 -0
  81. package/src/events/index.ts +32 -0
  82. package/src/events/service.ts +352 -0
  83. package/src/events/types.ts +223 -0
  84. package/src/index.ts +133 -1
  85. package/src/security/filter.ts +88 -8
  86. package/src/security/guards/builtin/auth-guard.ts +68 -0
  87. package/src/security/guards/builtin/index.ts +3 -0
  88. package/src/security/guards/builtin/roles-guard.ts +165 -0
  89. package/src/security/guards/decorators.ts +124 -0
  90. package/src/security/guards/execution-context.ts +152 -0
  91. package/src/security/guards/guard-registry.ts +164 -0
  92. package/src/security/guards/index.ts +7 -0
  93. package/src/security/guards/reflector.ts +99 -0
  94. package/src/security/guards/types.ts +144 -0
  95. package/src/security/index.ts +1 -0
  96. package/src/security/security-module.ts +72 -2
  97. package/src/validation/class-validator.ts +322 -0
  98. package/src/validation/custom-validator.ts +289 -0
  99. package/src/validation/errors.ts +50 -2
  100. package/src/validation/index.ts +103 -1
  101. package/src/validation/rules/array.ts +118 -0
  102. package/src/validation/rules/common.ts +286 -0
  103. package/src/validation/rules/conditional.ts +52 -0
  104. package/src/validation/rules/index.ts +51 -0
  105. package/src/validation/rules/object.ts +86 -0
  106. package/src/validation/types.ts +61 -1
  107. package/tests/di/global-module.test.ts +487 -0
  108. package/tests/events/event-decorators.test.ts +173 -0
  109. package/tests/events/event-emitter.test.ts +373 -0
  110. package/tests/events/event-module.test.ts +373 -0
  111. package/tests/security/guards/guards-integration.test.ts +371 -0
  112. package/tests/security/guards/guards.test.ts +775 -0
  113. package/tests/security/security-module.test.ts +2 -2
  114. package/tests/validation/class-validator.test.ts +349 -0
  115. package/tests/validation/custom-validator.test.ts +335 -0
  116. package/tests/validation/rules.test.ts +543 -0
package/docs/events.md ADDED
@@ -0,0 +1,494 @@
1
+ # Event System
2
+
3
+ The Event Module provides a powerful event-driven architecture for building loosely coupled, highly maintainable applications. It supports synchronous and asynchronous event handling, event priorities, and wildcard event patterns.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Core Concepts](#core-concepts)
10
+ - [Event Module Configuration](#event-module-configuration)
11
+ - [Publishing Events](#publishing-events)
12
+ - [Listening to Events](#listening-to-events)
13
+ - [Event Priority](#event-priority)
14
+ - [Async Event Handling](#async-event-handling)
15
+ - [Wildcard Events](#wildcard-events)
16
+ - [Error Handling](#error-handling)
17
+ - [Best Practices](#best-practices)
18
+ - [API Reference](#api-reference)
19
+
20
+ ## Installation
21
+
22
+ The Event Module is included in `@dangao/bun-server`. No additional installation is required.
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import {
28
+ Application,
29
+ Module,
30
+ Injectable,
31
+ Inject,
32
+ EventModule,
33
+ OnEvent,
34
+ EVENT_EMITTER_TOKEN,
35
+ } from '@dangao/bun-server';
36
+ import type { EventEmitter } from '@dangao/bun-server';
37
+
38
+ // Define event
39
+ const USER_CREATED = Symbol('user.created');
40
+
41
+ interface UserCreatedEvent {
42
+ userId: string;
43
+ email: string;
44
+ }
45
+
46
+ // Service that publishes events
47
+ @Injectable()
48
+ class UserService {
49
+ constructor(
50
+ @Inject(EVENT_EMITTER_TOKEN) private eventEmitter: EventEmitter,
51
+ ) {}
52
+
53
+ async createUser(email: string) {
54
+ const userId = 'user-123';
55
+
56
+ // Publish event
57
+ this.eventEmitter.emit<UserCreatedEvent>(USER_CREATED, {
58
+ userId,
59
+ email,
60
+ });
61
+
62
+ return { userId, email };
63
+ }
64
+ }
65
+
66
+ // Service that listens to events
67
+ @Injectable()
68
+ class NotificationService {
69
+ @OnEvent(USER_CREATED)
70
+ handleUserCreated(payload: UserCreatedEvent) {
71
+ console.log(`Welcome email sent to ${payload.email}`);
72
+ }
73
+ }
74
+
75
+ // Configure module
76
+ EventModule.forRoot({
77
+ maxListeners: 20,
78
+ });
79
+
80
+ EventModule.registerListeners([NotificationService]);
81
+
82
+ @Module({
83
+ imports: [EventModule],
84
+ providers: [UserService, NotificationService],
85
+ })
86
+ class AppModule {}
87
+
88
+ const app = new Application({ port: 3000 });
89
+ app.registerModule(AppModule);
90
+
91
+ // Initialize event listeners after module registration
92
+ EventModule.initializeListeners(app.getContainer());
93
+
94
+ app.listen(3000);
95
+ ```
96
+
97
+ ## Core Concepts
98
+
99
+ ### Event Names
100
+
101
+ Events can be identified by:
102
+
103
+ - **Symbol**: Recommended for type safety and avoiding naming conflicts
104
+ - **String**: Useful for dynamic events or wildcard matching
105
+
106
+ ```typescript
107
+ // Symbol event (recommended)
108
+ const USER_CREATED = Symbol('user.created');
109
+
110
+ // String event
111
+ const orderEvent = 'order.created';
112
+ ```
113
+
114
+ ### Event Payload
115
+
116
+ Events can carry any data as payload. It's recommended to define interfaces for type safety:
117
+
118
+ ```typescript
119
+ interface UserCreatedEvent {
120
+ userId: string;
121
+ email: string;
122
+ createdAt: Date;
123
+ }
124
+
125
+ // Publishing with typed payload
126
+ eventEmitter.emit<UserCreatedEvent>(USER_CREATED, {
127
+ userId: '123',
128
+ email: 'user@example.com',
129
+ createdAt: new Date(),
130
+ });
131
+ ```
132
+
133
+ ## Event Module Configuration
134
+
135
+ ```typescript
136
+ EventModule.forRoot({
137
+ // Enable wildcard event matching
138
+ wildcard: true, // default: false
139
+
140
+ // Delimiter for wildcard matching
141
+ delimiter: '.', // default: '.'
142
+
143
+ // Global prefix for all events
144
+ globalPrefix: 'app', // optional
145
+
146
+ // Maximum listeners per event (memory leak warning)
147
+ maxListeners: 10, // default: 10
148
+
149
+ // Custom error handler
150
+ onError: (error, event, payload) => {
151
+ console.error(`Error in event ${String(event)}:`, error);
152
+ },
153
+ });
154
+ ```
155
+
156
+ ## Publishing Events
157
+
158
+ ### Synchronous Publishing
159
+
160
+ Use `emit()` to publish events synchronously. Async listeners will be triggered but not awaited:
161
+
162
+ ```typescript
163
+ @Injectable()
164
+ class OrderService {
165
+ constructor(
166
+ @Inject(EVENT_EMITTER_TOKEN) private eventEmitter: EventEmitter,
167
+ ) {}
168
+
169
+ createOrder(userId: string, amount: number) {
170
+ const order = { id: 'order-1', userId, amount };
171
+
172
+ // Publish event (doesn't wait for async listeners)
173
+ this.eventEmitter.emit('order.created', {
174
+ orderId: order.id,
175
+ userId,
176
+ amount,
177
+ });
178
+
179
+ return order;
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Asynchronous Publishing
185
+
186
+ Use `emitAsync()` to wait for all listeners (including async ones) to complete:
187
+
188
+ ```typescript
189
+ async createOrder(userId: string, amount: number) {
190
+ const order = { id: 'order-1', userId, amount };
191
+
192
+ // Wait for all listeners to complete
193
+ await this.eventEmitter.emitAsync('order.created', {
194
+ orderId: order.id,
195
+ userId,
196
+ amount,
197
+ });
198
+
199
+ return order;
200
+ }
201
+ ```
202
+
203
+ ## Listening to Events
204
+
205
+ ### Using `@OnEvent()` Decorator
206
+
207
+ The recommended way to listen to events:
208
+
209
+ ```typescript
210
+ @Injectable()
211
+ class NotificationService {
212
+ @OnEvent('user.created')
213
+ handleUserCreated(payload: { email: string }) {
214
+ console.log(`Sending welcome email to ${payload.email}`);
215
+ }
216
+
217
+ @OnEvent(USER_DELETED)
218
+ async handleUserDeleted(payload: { userId: string }) {
219
+ await this.cleanupUserData(payload.userId);
220
+ }
221
+ }
222
+ ```
223
+
224
+ ### Manual Subscription
225
+
226
+ You can also subscribe manually using the EventEmitter:
227
+
228
+ ```typescript
229
+ @Injectable()
230
+ class DynamicListener {
231
+ constructor(
232
+ @Inject(EVENT_EMITTER_TOKEN) private eventEmitter: EventEmitter,
233
+ ) {
234
+ // Subscribe
235
+ const unsubscribe = this.eventEmitter.on('custom.event', (payload) => {
236
+ console.log('Received:', payload);
237
+ });
238
+
239
+ // Later: unsubscribe
240
+ // unsubscribe();
241
+ }
242
+ }
243
+ ```
244
+
245
+ ### One-time Listeners
246
+
247
+ ```typescript
248
+ // Using decorator (listener is removed after first call)
249
+ // Note: @OnEvent doesn't support once directly, use manual subscription
250
+
251
+ // Manual one-time subscription
252
+ this.eventEmitter.once('setup.complete', (payload) => {
253
+ console.log('Setup completed!');
254
+ });
255
+ ```
256
+
257
+ ## Event Priority
258
+
259
+ Higher priority listeners execute first:
260
+
261
+ ```typescript
262
+ @Injectable()
263
+ class HighPriorityService {
264
+ @OnEvent('order.created', { priority: 100 })
265
+ validateOrder(payload: OrderCreatedEvent) {
266
+ // Executes first
267
+ console.log('Validating order...');
268
+ }
269
+ }
270
+
271
+ @Injectable()
272
+ class LowPriorityService {
273
+ @OnEvent('order.created', { priority: 1 })
274
+ logOrder(payload: OrderCreatedEvent) {
275
+ // Executes last
276
+ console.log('Logging order...');
277
+ }
278
+ }
279
+ ```
280
+
281
+ Default priority is `0`. Listeners with the same priority execute in registration order.
282
+
283
+ ## Async Event Handling
284
+
285
+ Mark listeners as async to ensure proper error handling:
286
+
287
+ ```typescript
288
+ @Injectable()
289
+ class EmailService {
290
+ @OnEvent('user.created', { async: true })
291
+ async sendWelcomeEmail(payload: { email: string }) {
292
+ await this.mailer.send({
293
+ to: payload.email,
294
+ subject: 'Welcome!',
295
+ body: '...',
296
+ });
297
+ }
298
+ }
299
+ ```
300
+
301
+ When using `emit()`:
302
+ - Async listeners are triggered but not awaited
303
+ - Errors are caught and passed to the error handler
304
+
305
+ When using `emitAsync()`:
306
+ - All listeners (sync and async) are awaited
307
+ - All errors are caught and handled
308
+
309
+ ## Wildcard Events
310
+
311
+ Enable wildcard matching in the module configuration:
312
+
313
+ ```typescript
314
+ EventModule.forRoot({
315
+ wildcard: true,
316
+ });
317
+ ```
318
+
319
+ ### Single Wildcard (`*`)
320
+
321
+ Matches exactly one segment:
322
+
323
+ ```typescript
324
+ @OnEvent('user.*')
325
+ handleAnyUserEvent(payload: unknown) {
326
+ // Matches: user.created, user.updated, user.deleted
327
+ // Does NOT match: user.profile.updated
328
+ }
329
+ ```
330
+
331
+ ### Double Wildcard (`**`)
332
+
333
+ Matches any number of segments:
334
+
335
+ ```typescript
336
+ @OnEvent('order.**')
337
+ handleAllOrderEvents(payload: unknown) {
338
+ // Matches: order.created, order.item.added, order.payment.completed
339
+ }
340
+ ```
341
+
342
+ ## Error Handling
343
+
344
+ ### Global Error Handler
345
+
346
+ ```typescript
347
+ EventModule.forRoot({
348
+ onError: (error, event, payload) => {
349
+ // Log to monitoring service
350
+ console.error(`Event handler error for ${String(event)}:`, error);
351
+
352
+ // Optionally re-throw or handle
353
+ },
354
+ });
355
+ ```
356
+
357
+ ### Try-Catch in Listeners
358
+
359
+ ```typescript
360
+ @OnEvent('risky.event', { async: true })
361
+ async handleRiskyEvent(payload: unknown) {
362
+ try {
363
+ await this.riskyOperation(payload);
364
+ } catch (error) {
365
+ // Handle or log error
366
+ console.error('Failed to process event:', error);
367
+ // Optionally re-throw to trigger global error handler
368
+ throw error;
369
+ }
370
+ }
371
+ ```
372
+
373
+ ## Best Practices
374
+
375
+ ### 1. Use Symbols for Event Names
376
+
377
+ ```typescript
378
+ // Good: Type-safe, no naming conflicts
379
+ export const USER_CREATED = Symbol('user.created');
380
+
381
+ // Avoid: Can conflict with other events
382
+ const eventName = 'user.created';
383
+ ```
384
+
385
+ ### 2. Define Event Payload Interfaces
386
+
387
+ ```typescript
388
+ // Good: Clear contract
389
+ export interface UserCreatedEvent {
390
+ userId: string;
391
+ email: string;
392
+ createdAt: Date;
393
+ }
394
+
395
+ // Avoid: Unclear payload structure
396
+ eventEmitter.emit(USER_CREATED, { userId, email, date: new Date() });
397
+ ```
398
+
399
+ ### 3. Keep Listeners Focused
400
+
401
+ ```typescript
402
+ // Good: Single responsibility
403
+ @OnEvent(USER_CREATED)
404
+ sendWelcomeEmail(payload: UserCreatedEvent) { ... }
405
+
406
+ @OnEvent(USER_CREATED)
407
+ trackUserSignup(payload: UserCreatedEvent) { ... }
408
+
409
+ // Avoid: Too many responsibilities
410
+ @OnEvent(USER_CREATED)
411
+ handleUserCreated(payload: UserCreatedEvent) {
412
+ this.sendEmail();
413
+ this.trackAnalytics();
414
+ this.notifyAdmin();
415
+ this.createDefaultSettings();
416
+ }
417
+ ```
418
+
419
+ ### 4. Handle Errors Gracefully
420
+
421
+ ```typescript
422
+ // Good: Errors don't break other listeners
423
+ @OnEvent('payment.failed', { async: true })
424
+ async handlePaymentFailure(payload: PaymentFailedEvent) {
425
+ try {
426
+ await this.notifyUser(payload.userId);
427
+ } catch (error) {
428
+ this.logger.error('Failed to notify user', error);
429
+ // Don't re-throw unless necessary
430
+ }
431
+ }
432
+ ```
433
+
434
+ ### 5. Use Priority Wisely
435
+
436
+ ```typescript
437
+ // Good: Validation before processing
438
+ @OnEvent('order.submitted', { priority: 100 })
439
+ validateOrder(payload: OrderEvent) { ... }
440
+
441
+ @OnEvent('order.submitted', { priority: 50 })
442
+ processOrder(payload: OrderEvent) { ... }
443
+
444
+ @OnEvent('order.submitted', { priority: 1 })
445
+ logOrder(payload: OrderEvent) { ... }
446
+ ```
447
+
448
+ ## API Reference
449
+
450
+ ### EventModule
451
+
452
+ | Method | Description |
453
+ |--------|-------------|
454
+ | `forRoot(options?)` | Configure the event module |
455
+ | `registerListeners(classes)` | Register listener classes |
456
+ | `initializeListeners(container, additional?)` | Initialize and scan listeners |
457
+ | `getEventEmitter(container)` | Get the EventEmitter instance |
458
+
459
+ ### EventEmitter
460
+
461
+ | Method | Description |
462
+ |--------|-------------|
463
+ | `emit(event, payload)` | Publish event synchronously |
464
+ | `emitAsync(event, payload)` | Publish event and wait for all listeners |
465
+ | `on(event, listener, options?)` | Subscribe to an event |
466
+ | `once(event, listener, options?)` | Subscribe once to an event |
467
+ | `off(event, listener)` | Unsubscribe from an event |
468
+ | `removeAllListeners(event?)` | Remove all listeners |
469
+ | `listenerCount(event)` | Get listener count for an event |
470
+ | `eventNames()` | Get all registered event names |
471
+
472
+ ### @OnEvent Decorator
473
+
474
+ ```typescript
475
+ @OnEvent(event: string | symbol, options?: {
476
+ async?: boolean; // default: false
477
+ priority?: number; // default: 0
478
+ })
479
+ ```
480
+
481
+ ### EventModuleOptions
482
+
483
+ | Option | Type | Default | Description |
484
+ |--------|------|---------|-------------|
485
+ | `wildcard` | `boolean` | `false` | Enable wildcard matching |
486
+ | `delimiter` | `string` | `'.'` | Wildcard delimiter |
487
+ | `globalPrefix` | `string` | - | Prefix for all events |
488
+ | `maxListeners` | `number` | `10` | Max listeners per event |
489
+ | `onError` | `function` | - | Global error handler |
490
+
491
+ ## Related
492
+
493
+ - [Guide](./guide.md) - Framework guide
494
+ - [Request Lifecycle](./request-lifecycle.md) - Understanding the request flow