@dangao/bun-server 1.1.2 → 1.1.4

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.
package/docs/guide.md ADDED
@@ -0,0 +1,634 @@
1
+ # Usage Guide
2
+
3
+ Covers key steps for building Bun Server applications from scratch.
4
+
5
+ ## 1. Initialize Application
6
+
7
+ ```ts
8
+ import "reflect-metadata";
9
+ import { Application } from "@dangao/bun-server";
10
+
11
+ const app = new Application({ port: 3000 });
12
+ app.listen();
13
+ ```
14
+
15
+ > Tip: Default port is 3000, can be adjusted via `app.listen(customPort)` or
16
+ > `new Application({ port })`.
17
+
18
+ ## 2. Register Controllers and Dependencies
19
+
20
+ ```ts
21
+ import {
22
+ Application,
23
+ Body,
24
+ Controller,
25
+ GET,
26
+ Injectable,
27
+ Param,
28
+ POST,
29
+ } from "@dangao/bun-server";
30
+
31
+ @Injectable()
32
+ class UserService {
33
+ public findById(id: string) {
34
+ return { id, name: "Alice" };
35
+ }
36
+ }
37
+
38
+ @Controller("/api/users")
39
+ class UserController {
40
+ public constructor(private readonly userService: UserService) {}
41
+
42
+ @GET("/:id")
43
+ public getUser(@Param("id") id: string) {
44
+ return this.userService.findById(id);
45
+ }
46
+ }
47
+
48
+ const app = new Application({ port: 3000 });
49
+ app.getContainer().register(UserService);
50
+ app.registerController(UserController);
51
+ app.listen();
52
+ ```
53
+
54
+ ## 3. Using Middleware
55
+
56
+ ```ts
57
+ import {
58
+ createCorsMiddleware,
59
+ createLoggerMiddleware,
60
+ } from "@dangao/bun-server";
61
+
62
+ const app = new Application({ port: 3000 });
63
+
64
+ // Global middleware
65
+ app.use(createLoggerMiddleware({ prefix: "[App]" }));
66
+ app.use(createCorsMiddleware({ origin: "*" }));
67
+
68
+ // Custom middleware
69
+ app.use(async (ctx, next) => {
70
+ console.log("Before request");
71
+ const response = await next();
72
+ console.log("After request");
73
+ return response;
74
+ });
75
+ ```
76
+
77
+ ## 4. Parameter Validation
78
+
79
+ ```ts
80
+ import { IsEmail, IsString, MinLength, Validate } from "@dangao/bun-server";
81
+
82
+ @Controller("/api/users")
83
+ class UserController {
84
+ @POST("/")
85
+ public createUser(
86
+ @Body("name") @Validate(IsString(), MinLength(3)) name: string,
87
+ @Body("email") @Validate(IsEmail()) email: string,
88
+ ) {
89
+ return { name, email };
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## 5. WebSocket Gateway
95
+
96
+ ```ts
97
+ import { OnMessage, WebSocketGateway } from "@dangao/bun-server";
98
+
99
+ @WebSocketGateway("/ws")
100
+ class ChatGateway {
101
+ @OnMessage
102
+ public handleMessage(ws: ServerWebSocket, message: string) {
103
+ ws.send(`Echo: ${message}`);
104
+ }
105
+ }
106
+
107
+ const app = new Application({ port: 3000 });
108
+ app.registerWebSocketGateway(ChatGateway);
109
+ app.listen();
110
+ ```
111
+
112
+ ## 6. File Upload and Static Resources
113
+
114
+ ```ts
115
+ import {
116
+ createFileUploadMiddleware,
117
+ createStaticFileMiddleware,
118
+ } from "@dangao/bun-server";
119
+
120
+ const app = new Application({ port: 3000 });
121
+
122
+ // File upload
123
+ app.use(createFileUploadMiddleware({ maxSize: 5 * 1024 * 1024 }));
124
+
125
+ // Static files
126
+ app.use(createStaticFileMiddleware({ root: "./public", prefix: "/assets" }));
127
+ ```
128
+
129
+ ## 7. Error Handling and Custom Filters
130
+
131
+ ```ts
132
+ import { ExceptionFilterRegistry, HttpException } from "@dangao/bun-server";
133
+
134
+ @Controller("/api")
135
+ class ApiController {
136
+ @GET("/error")
137
+ public throwError() {
138
+ throw new HttpException(400, "Bad Request");
139
+ }
140
+ }
141
+
142
+ // Register custom exception filter
143
+ ExceptionFilterRegistry.register(HttpException, (error, ctx) => {
144
+ return ctx.createResponse({ error: error.message }, { status: error.status });
145
+ });
146
+ ```
147
+
148
+ ## 8. Extension System
149
+
150
+ Bun Server provides multiple extension methods, including middleware,
151
+ application extensions, module system, etc. For detailed information, please
152
+ refer to [Extension System Documentation](./extensions.md).
153
+
154
+ ### Quick Examples
155
+
156
+ #### Using Module Approach (Recommended)
157
+
158
+ ```typescript
159
+ import {
160
+ LoggerModule,
161
+ LogLevel,
162
+ Module,
163
+ SwaggerModule,
164
+ } from "@dangao/bun-server";
165
+
166
+ // Configure modules
167
+ LoggerModule.forRoot({
168
+ logger: { prefix: "App", level: LogLevel.INFO },
169
+ enableRequestLogging: true,
170
+ });
171
+
172
+ SwaggerModule.forRoot({
173
+ info: { title: "API", version: "1.0.0" },
174
+ uiPath: "/swagger",
175
+ });
176
+
177
+ @Module({
178
+ imports: [LoggerModule, SwaggerModule],
179
+ controllers: [UserController],
180
+ providers: [UserService],
181
+ })
182
+ class AppModule {}
183
+
184
+ const app = new Application({ port: 3000 });
185
+ app.registerModule(AppModule);
186
+ ```
187
+
188
+ #### Using Extension Approach
189
+
190
+ ```typescript
191
+ import { LoggerExtension, SwaggerExtension } from "@dangao/bun-server";
192
+
193
+ const app = new Application({ port: 3000 });
194
+
195
+ app.registerExtension(new LoggerExtension({ prefix: "App" }));
196
+ app.registerExtension(
197
+ new SwaggerExtension({
198
+ info: { title: "API", version: "1.0.0" },
199
+ }),
200
+ );
201
+ ```
202
+
203
+ #### Using Middleware
204
+
205
+ ```typescript
206
+ import {
207
+ createCorsMiddleware,
208
+ createLoggerMiddleware,
209
+ } from "@dangao/bun-server";
210
+
211
+ const app = new Application({ port: 3000 });
212
+
213
+ app.use(createLoggerMiddleware({ prefix: "[App]" }));
214
+ app.use(createCorsMiddleware({ origin: "*" }));
215
+ ```
216
+
217
+ For more extension methods and use cases, please refer to
218
+ [Extension System Documentation](./extensions.md).
219
+
220
+ ### Advanced Example: Interface + Symbol + Module
221
+
222
+ This example demonstrates using interfaces with Symbol tokens and module-based
223
+ dependency injection for more flexible decoupled design:
224
+
225
+ ```typescript
226
+ import {
227
+ Application,
228
+ Body,
229
+ CONFIG_SERVICE_TOKEN,
230
+ ConfigModule,
231
+ ConfigService,
232
+ Controller,
233
+ GET,
234
+ Inject,
235
+ Injectable,
236
+ Module,
237
+ Param,
238
+ POST,
239
+ } from "@dangao/bun-server";
240
+
241
+ // Define service interface
242
+ interface UserService {
243
+ find(id: string): Promise<{ id: string; name: string } | undefined>;
244
+ create(name: string): { id: string; name: string };
245
+ }
246
+
247
+ // Create Symbol token for DI
248
+ const UserService = Symbol("UserService");
249
+
250
+ // Implement the interface
251
+ @Injectable()
252
+ class UserServiceImpl implements UserService {
253
+ private readonly users = new Map<string, { id: string; name: string }>([
254
+ ["1", { id: "1", name: "Alice" }],
255
+ ]);
256
+
257
+ public async find(id: string) {
258
+ return this.users.get(id);
259
+ }
260
+
261
+ public create(name: string) {
262
+ const id = String(this.users.size + 1);
263
+ const user = { id, name };
264
+ this.users.set(id, user);
265
+ return user;
266
+ }
267
+ }
268
+
269
+ @Controller("/api/users")
270
+ class UserController {
271
+ public constructor(
272
+ private readonly service: UserService,
273
+ @Inject(CONFIG_SERVICE_TOKEN) private readonly config: ConfigService,
274
+ ) {}
275
+
276
+ @GET("/:id")
277
+ public async getUser(@Param("id") id: string) {
278
+ const user = await this.service.find(id);
279
+ if (!user) {
280
+ return { error: "Not Found" };
281
+ }
282
+ return user;
283
+ }
284
+
285
+ @POST("/")
286
+ public createUser(@Body("name") name: string) {
287
+ return this.service.create(name);
288
+ }
289
+ }
290
+
291
+ // Define module with Symbol-based provider
292
+ @Module({
293
+ controllers: [UserController],
294
+ providers: [
295
+ {
296
+ provide: UserService,
297
+ useClass: UserServiceImpl,
298
+ },
299
+ ],
300
+ exports: [UserService],
301
+ })
302
+ class UserModule {}
303
+
304
+ // Configure modules
305
+ ConfigModule.forRoot({
306
+ defaultConfig: {
307
+ app: {
308
+ name: "Advanced App",
309
+ port: 3100,
310
+ },
311
+ },
312
+ });
313
+
314
+ // Register module and start application
315
+ @Module({
316
+ imports: [ConfigModule],
317
+ controllers: [UserController],
318
+ providers: [
319
+ {
320
+ provide: UserService,
321
+ useClass: UserServiceImpl,
322
+ },
323
+ ],
324
+ })
325
+ class AppModule {}
326
+
327
+ const app = new Application({ port: 3100 });
328
+ app.registerModule(AppModule);
329
+ app.listen();
330
+ ```
331
+
332
+ **Key points:**
333
+
334
+ - **Interface-based design**: Define contracts with TypeScript interfaces for
335
+ better decoupling and testing
336
+ - **Symbol tokens**: Use `Symbol()` for type-safe dependency injection tokens,
337
+ avoiding naming conflicts with string tokens
338
+ - **Module providers**: Register providers using
339
+ `provide: Symbol, useClass: Implementation` to separate interface from
340
+ implementation
341
+ - **Type-safe injection**: Use interface types directly in constructors, the
342
+ framework will automatically resolve the implementation via Symbol token
343
+
344
+ This pattern is especially suitable for large projects, allowing easy
345
+ implementation replacement without affecting consumer code.
346
+
347
+ ## 9. Database Integration
348
+
349
+ ### Basic Database Connection
350
+
351
+ ```ts
352
+ import {
353
+ DATABASE_SERVICE_TOKEN,
354
+ DatabaseModule,
355
+ DatabaseService,
356
+ Inject,
357
+ } from "@dangao/bun-server";
358
+
359
+ // Configure database
360
+ DatabaseModule.forRoot({
361
+ database: {
362
+ type: "postgres", // or 'mysql', 'sqlite'
363
+ config: {
364
+ host: "localhost",
365
+ port: 5432,
366
+ database: "mydb",
367
+ user: "user",
368
+ password: "password",
369
+ },
370
+ },
371
+ });
372
+
373
+ @Injectable()
374
+ class UserService {
375
+ public constructor(
376
+ @Inject(DATABASE_SERVICE_TOKEN) private readonly db: DatabaseService,
377
+ ) {}
378
+
379
+ public async findUser(id: string) {
380
+ const result = await this.db.query("SELECT * FROM users WHERE id = $1", [
381
+ id,
382
+ ]);
383
+ return result[0];
384
+ }
385
+ }
386
+ ```
387
+
388
+ ### Using ORM with Drizzle
389
+
390
+ ```ts
391
+ import {
392
+ Column,
393
+ DrizzleBaseRepository,
394
+ Entity,
395
+ PrimaryKey,
396
+ Repository,
397
+ } from "@dangao/bun-server";
398
+
399
+ @Entity("users")
400
+ class User {
401
+ @PrimaryKey()
402
+ @Column({ type: "INTEGER", autoIncrement: true })
403
+ public id!: number;
404
+
405
+ @Column({ type: "TEXT", nullable: false })
406
+ public name!: string;
407
+ }
408
+
409
+ @Repository("users", "id")
410
+ class UserRepository extends DrizzleBaseRepository<User> {}
411
+
412
+ @Injectable()
413
+ class UserService {
414
+ public constructor(
415
+ @Inject(UserRepository) private readonly repo: UserRepository,
416
+ ) {}
417
+
418
+ public async findAll() {
419
+ return await this.repo.findAll();
420
+ }
421
+ }
422
+ ```
423
+
424
+ ### Using Transactions
425
+
426
+ ```ts
427
+ import { IsolationLevel, Propagation, Transactional } from "@dangao/bun-server";
428
+
429
+ @Injectable()
430
+ class OrderService {
431
+ @Transactional({
432
+ propagation: Propagation.REQUIRED,
433
+ isolationLevel: IsolationLevel.READ_COMMITTED,
434
+ })
435
+ public async createOrder(orderData: OrderData) {
436
+ // All database operations run in a transaction
437
+ await this.db.query("INSERT INTO orders ...");
438
+ await this.db.query("INSERT INTO order_items ...");
439
+ }
440
+ }
441
+ ```
442
+
443
+ ## 10. Caching
444
+
445
+ ```ts
446
+ import {
447
+ CACHE_SERVICE_TOKEN,
448
+ Cacheable,
449
+ CacheEvict,
450
+ CacheModule,
451
+ CacheService,
452
+ } from "@dangao/bun-server";
453
+
454
+ CacheModule.forRoot({
455
+ defaultTtl: 3600000, // 1 hour
456
+ });
457
+
458
+ @Injectable()
459
+ class ProductService {
460
+ public constructor(
461
+ @Inject(CACHE_SERVICE_TOKEN) private readonly cache: CacheService,
462
+ ) {}
463
+
464
+ @Cacheable("product", 60000)
465
+ public async getProduct(id: string) {
466
+ // Expensive operation - result is cached
467
+ return await this.db.query("SELECT * FROM products WHERE id = $1", [id]);
468
+ }
469
+
470
+ @CacheEvict("product")
471
+ public async updateProduct(id: string, data: ProductData) {
472
+ await this.db.query("UPDATE products ...");
473
+ }
474
+ }
475
+ ```
476
+
477
+ ## 11. Job Queue
478
+
479
+ ```ts
480
+ import {
481
+ Cron,
482
+ Queue,
483
+ QUEUE_SERVICE_TOKEN,
484
+ QueueModule,
485
+ QueueService,
486
+ } from "@dangao/bun-server";
487
+
488
+ QueueModule.forRoot({
489
+ defaultRetries: 3,
490
+ });
491
+
492
+ @Injectable()
493
+ class EmailService {
494
+ @Queue("send-email")
495
+ public async sendEmail(data: { to: string; subject: string }) {
496
+ // Email sending logic
497
+ }
498
+
499
+ @Cron("0 0 * * *") // Daily at midnight
500
+ public async sendDailyReport() {
501
+ // Send daily report
502
+ }
503
+ }
504
+ ```
505
+
506
+ ## 12. Session Management
507
+
508
+ ```ts
509
+ import {
510
+ createSessionMiddleware,
511
+ Session,
512
+ SESSION_SERVICE_TOKEN,
513
+ SessionModule,
514
+ SessionService,
515
+ } from "@dangao/bun-server";
516
+
517
+ SessionModule.forRoot({
518
+ secret: "your-secret-key",
519
+ maxAge: 3600000, // 1 hour
520
+ });
521
+
522
+ // Add session middleware
523
+ app.use(createSessionMiddleware());
524
+
525
+ @Controller("/api/auth")
526
+ class AuthController {
527
+ public constructor(
528
+ @Inject(SESSION_SERVICE_TOKEN) private readonly session: SessionService,
529
+ ) {}
530
+
531
+ @POST("/login")
532
+ public async login(@Body() credentials: LoginDto, @Session() session: any) {
533
+ // Session is automatically injected
534
+ session.userId = credentials.userId;
535
+ return { success: true };
536
+ }
537
+ }
538
+ ```
539
+
540
+ ## 13. Health Checks
541
+
542
+ ```ts
543
+ import { HealthModule } from "@dangao/bun-server";
544
+
545
+ HealthModule.forRoot({
546
+ indicators: [
547
+ {
548
+ name: "database",
549
+ check: async () => {
550
+ const isHealthy = await dbService.healthCheck();
551
+ return { status: isHealthy ? "up" : "down" };
552
+ },
553
+ },
554
+ ],
555
+ });
556
+
557
+ // Automatically provides /health and /ready endpoints
558
+ ```
559
+
560
+ ## 14. Metrics Collection
561
+
562
+ ```ts
563
+ import {
564
+ createHttpMetricsMiddleware,
565
+ METRICS_SERVICE_TOKEN,
566
+ MetricsCollector,
567
+ MetricsModule,
568
+ } from "@dangao/bun-server";
569
+
570
+ MetricsModule.forRoot({
571
+ enableHttpMetrics: true,
572
+ });
573
+
574
+ // Add metrics middleware
575
+ app.use(createHttpMetricsMiddleware());
576
+
577
+ @Injectable()
578
+ class OrderService {
579
+ public constructor(
580
+ @Inject(METRICS_SERVICE_TOKEN) private readonly metrics: MetricsCollector,
581
+ ) {}
582
+
583
+ public async createOrder() {
584
+ this.metrics.increment("orders.created");
585
+ // Create order logic
586
+ }
587
+ }
588
+ ```
589
+
590
+ ## 15. Security and Authentication
591
+
592
+ ```ts
593
+ import {
594
+ Auth,
595
+ SecurityContextHolder,
596
+ SecurityModule,
597
+ } from "@dangao/bun-server";
598
+
599
+ SecurityModule.forRoot({
600
+ jwt: {
601
+ secret: "your-secret-key",
602
+ accessTokenExpiresIn: 3600,
603
+ },
604
+ });
605
+
606
+ @Controller("/api/users")
607
+ class UserController {
608
+ @GET("/profile")
609
+ @Auth() // Require authentication
610
+ public getProfile() {
611
+ const context = SecurityContextHolder.getContext();
612
+ return context.getPrincipal();
613
+ }
614
+
615
+ @GET("/admin")
616
+ @Auth({ roles: ["admin"] }) // Require admin role
617
+ public getAdmin() {
618
+ return { message: "Admin access" };
619
+ }
620
+ }
621
+ ```
622
+
623
+ ## 16. Testing Recommendations
624
+
625
+ - Use `tests/utils/test-port.ts` to get auto-incrementing ports, avoiding local
626
+ conflicts.
627
+ - Call `RouteRegistry.getInstance().clear()` and
628
+ `ControllerRegistry.getInstance().clear()` in `afterEach` hooks to keep global
629
+ state clean.
630
+ - In end-to-end tests, you can directly instantiate `Context` and call
631
+ `router.handle(context)` without actually starting the server.
632
+ - For database integration tests, use environment variables (`POSTGRES_URL`,
633
+ `MYSQL_URL`) to configure connections, tests will automatically skip if not
634
+ configured.
@@ -0,0 +1,10 @@
1
+ # Migration Guide (English Draft)
2
+
3
+ A full translation of `docs/migration.md` is in progress. Planned sections:
4
+
5
+ - Migrating from Express / Koa / NestJS
6
+ - Moving from Node.js to Bun
7
+ - Updating legacy middleware
8
+ - Breaking changes per release
9
+
10
+ Please consult the Chinese version for now. If you would like to help, open an issue or PR.