@krisanalfa/bunest-adapter 0.1.0 → 0.4.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.
- package/README.md +515 -12
- package/dist/bun.adapter.d.ts +9 -48
- package/dist/bun.internal.types.d.ts +22 -0
- package/dist/bun.preflight-http-server.d.ts +44 -0
- package/dist/bun.request.d.ts +41 -1
- package/dist/bun.response.d.ts +28 -0
- package/dist/bun.server-instance.d.ts +131 -0
- package/dist/bun.ws-adapter.d.ts +38 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +652 -237
- package/dist/index.js.map +12 -9
- package/package.json +16 -7
package/README.md
CHANGED
|
@@ -2,6 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
This project provides a native Bun adapter for NestJS, allowing developers to leverage the performance benefits of the Bun runtime while using the powerful features of the NestJS framework.
|
|
4
4
|
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Native Bun adapter for NestJS](#native-bun-adapter-for-nestjs)
|
|
9
|
+
- [Full NestJS Feature Support](#full-nestjs-feature-support)
|
|
10
|
+
- [Controllers & HTTP Methods](#controllers--http-methods)
|
|
11
|
+
- [Middleware](#middleware)
|
|
12
|
+
- [Guards](#guards)
|
|
13
|
+
- [Interceptors](#interceptors)
|
|
14
|
+
- [Exception Filters](#exception-filters)
|
|
15
|
+
- [Validation](#validation)
|
|
16
|
+
- [File Uploads](#file-uploads)
|
|
17
|
+
- [Streaming Responses](#streaming-responses)
|
|
18
|
+
- [Server-Sent Events (SSE)](#server-sent-events-sse)
|
|
19
|
+
- [Versioning](#versioning)
|
|
20
|
+
- [CORS](#cors)
|
|
21
|
+
- [Cookies](#cookies)
|
|
22
|
+
- [Popular Express Middleware](#popular-express-middleware)
|
|
23
|
+
- [Bun File API Support](#bun-file-api-support)
|
|
24
|
+
- [BunFileInterceptor](#bunfileinterceptor)
|
|
25
|
+
- [WebSocket Support](#websocket-support)
|
|
26
|
+
- [Basic WebSocket Gateway](#basic-websocket-gateway)
|
|
27
|
+
- [WebSocket with Guards](#websocket-with-guards)
|
|
28
|
+
- [WebSocket with Pipes](#websocket-with-pipes)
|
|
29
|
+
- [WebSocket with Exception Filters](#websocket-with-exception-filters)
|
|
30
|
+
- [Broadcasting Messages](#broadcasting-messages)
|
|
31
|
+
- [Secure WebSocket (WSS)](#secure-websocket-wss)
|
|
32
|
+
- [Limitations](#limitations)
|
|
33
|
+
- [HTTPS](#https)
|
|
34
|
+
- [Code Quality](#code-quality)
|
|
35
|
+
- [Request / Response Objects](#request--response-objects)
|
|
36
|
+
- [BunRequest](#bunrequest)
|
|
37
|
+
- [BunResponse](#bunresponse)
|
|
38
|
+
- [Benchmark Results](#benchmark-results)
|
|
39
|
+
- [HTTP Benchmark](#http-benchmark)
|
|
40
|
+
- [WebSocket Benchmark](#websocket-benchmark)
|
|
41
|
+
- [Running HTTP Benchmark](#running-http-benchmark)
|
|
42
|
+
- [Running WebSocket Benchmark](#running-websocket-benchmark)
|
|
43
|
+
- [Contributing](#contributing)
|
|
44
|
+
- [Future Plans](#future-plans)
|
|
45
|
+
- [License](#license)
|
|
46
|
+
|
|
5
47
|
## Features
|
|
6
48
|
|
|
7
49
|
### Native Bun adapter for NestJS
|
|
@@ -334,6 +376,67 @@ class FilesController {
|
|
|
334
376
|
}
|
|
335
377
|
```
|
|
336
378
|
|
|
379
|
+
#### Server-Sent Events (SSE)
|
|
380
|
+
|
|
381
|
+
Full support for [Server-Sent Events](https://docs.nestjs.com/techniques/server-sent-events) using the `@Sse()` decorator. SSE allows servers to push real-time updates to clients over HTTP:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
import { Controller, Sse, MessageEvent } from '@nestjs/common';
|
|
385
|
+
import { Observable, interval, map } from 'rxjs';
|
|
386
|
+
|
|
387
|
+
@Controller()
|
|
388
|
+
class EventsController {
|
|
389
|
+
@Sse('/sse')
|
|
390
|
+
sendEvents(): Observable<MessageEvent> {
|
|
391
|
+
return interval(1000).pipe(
|
|
392
|
+
map(num => ({
|
|
393
|
+
data: `SSE message ${num.toString()}`,
|
|
394
|
+
})),
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Client Connection Example:**
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
const eventSource = new EventSource('http://localhost:3000/sse');
|
|
404
|
+
|
|
405
|
+
eventSource.onopen = () => {
|
|
406
|
+
console.log('SSE connection opened');
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
eventSource.onmessage = (event) => {
|
|
410
|
+
console.log('Received:', event.data); // "SSE message 0", "SSE message 1", etc.
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
eventSource.onerror = (error) => {
|
|
414
|
+
console.error('SSE error:', error);
|
|
415
|
+
eventSource.close();
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// Close the connection when done
|
|
419
|
+
// eventSource.close();
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**For HTTPS/Secure Connections:**
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import { EventSource } from 'eventsource'; // npm package for Node.js
|
|
426
|
+
|
|
427
|
+
const eventSource = new EventSource('https://localhost:3000/sse', {
|
|
428
|
+
fetch: (url, init) => fetch(url, {
|
|
429
|
+
...init,
|
|
430
|
+
tls: { rejectUnauthorized: false }, // For self-signed certificates
|
|
431
|
+
}),
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
eventSource.onmessage = (event) => {
|
|
435
|
+
console.log('Received:', event.data);
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
|
|
337
440
|
#### Versioning
|
|
338
441
|
|
|
339
442
|
Full API [versioning](https://docs.nestjs.com/techniques/versioning) support (URI, Header, Media Type, Custom):
|
|
@@ -442,6 +545,343 @@ Tested and working with:
|
|
|
442
545
|
- `cors` - CORS handling
|
|
443
546
|
- And most other Express-compatible middleware
|
|
444
547
|
|
|
548
|
+
### WebSocket Support
|
|
549
|
+
|
|
550
|
+
The Bun adapter provides full WebSocket support using Bun's native WebSocket implementation. The `BunWsAdapter` enables real-time, bidirectional communication between clients and servers with excellent performance.
|
|
551
|
+
|
|
552
|
+
#### Basic WebSocket Gateway
|
|
553
|
+
|
|
554
|
+
Create WebSocket gateways using NestJS decorators:
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
import { BunWsAdapter, BunAdapter } from "@krisanalfa/bunest-adapter";
|
|
558
|
+
import {
|
|
559
|
+
WebSocketGateway,
|
|
560
|
+
SubscribeMessage,
|
|
561
|
+
MessageBody,
|
|
562
|
+
OnGatewayConnection,
|
|
563
|
+
OnGatewayDisconnect,
|
|
564
|
+
ConnectedSocket,
|
|
565
|
+
} from "@nestjs/websockets";
|
|
566
|
+
import { ServerWebSocket } from "bun";
|
|
567
|
+
|
|
568
|
+
@WebSocketGateway({ cors: true })
|
|
569
|
+
class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|
570
|
+
handleConnection(client: ServerWebSocket) {
|
|
571
|
+
client.send(JSON.stringify({ event: "welcome", data: "Welcome!" }));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
handleDisconnect(client: ServerWebSocket) {
|
|
575
|
+
console.log("Client disconnected");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
@SubscribeMessage("message")
|
|
579
|
+
handleMessage(@MessageBody() data: string) {
|
|
580
|
+
return {
|
|
581
|
+
event: "message",
|
|
582
|
+
data: `Received: ${data}`,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Enable WebSocket support in your application
|
|
588
|
+
const app = await NestFactory.create(AppModule, new BunAdapter());
|
|
589
|
+
app.useWebSocketAdapter(new BunWsAdapter(app));
|
|
590
|
+
await app.listen(3000);
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Connect from the client:
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
const socket = new WebSocket("ws://localhost:3000");
|
|
597
|
+
|
|
598
|
+
socket.onopen = () => {
|
|
599
|
+
socket.send(JSON.stringify({ event: "message", data: "Hello!" }));
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
socket.onmessage = (event) => {
|
|
603
|
+
const data = JSON.parse(event.data);
|
|
604
|
+
console.log(data); // { event: 'message', data: 'Received: Hello!' }
|
|
605
|
+
};
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
#### WebSocket with Guards
|
|
609
|
+
|
|
610
|
+
Protect WebSocket endpoints with guards:
|
|
611
|
+
|
|
612
|
+
```ts
|
|
613
|
+
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
|
|
614
|
+
import { WsException } from "@nestjs/websockets";
|
|
615
|
+
|
|
616
|
+
@Injectable()
|
|
617
|
+
class WsAuthGuard implements CanActivate {
|
|
618
|
+
canActivate(context: ExecutionContext): boolean {
|
|
619
|
+
const data = context.switchToWs().getData<{ token?: string }>();
|
|
620
|
+
|
|
621
|
+
if (data.token !== "valid-token") {
|
|
622
|
+
throw new WsException("Unauthorized");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
@WebSocketGateway()
|
|
630
|
+
@UseGuards(WsAuthGuard)
|
|
631
|
+
class ProtectedGateway {
|
|
632
|
+
@SubscribeMessage("protected")
|
|
633
|
+
handleProtected(@MessageBody() data: { message: string }) {
|
|
634
|
+
return {
|
|
635
|
+
event: "protected",
|
|
636
|
+
data: `Protected message: ${data.message}`,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### WebSocket with Pipes
|
|
643
|
+
|
|
644
|
+
Validate WebSocket messages using pipes:
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
import { UsePipes, ValidationPipe } from "@nestjs/common";
|
|
648
|
+
import { IsString, IsNotEmpty, MinLength } from "class-validator";
|
|
649
|
+
|
|
650
|
+
class CreateMessageDto {
|
|
651
|
+
@IsString()
|
|
652
|
+
@IsNotEmpty()
|
|
653
|
+
@MinLength(3)
|
|
654
|
+
text!: string;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
@WebSocketGateway()
|
|
658
|
+
@UsePipes(
|
|
659
|
+
new ValidationPipe({
|
|
660
|
+
exceptionFactory: (errors) => new WsException(errors),
|
|
661
|
+
}),
|
|
662
|
+
)
|
|
663
|
+
class ValidationGateway {
|
|
664
|
+
@SubscribeMessage("createMessage")
|
|
665
|
+
handleCreateMessage(@MessageBody() dto: CreateMessageDto) {
|
|
666
|
+
return {
|
|
667
|
+
event: "messageCreated",
|
|
668
|
+
data: dto.text,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
#### WebSocket with Exception Filters
|
|
675
|
+
|
|
676
|
+
Handle WebSocket exceptions with custom filters:
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
import { Catch, ArgumentsHost, WsExceptionFilter } from "@nestjs/common";
|
|
680
|
+
import { WsException } from "@nestjs/websockets";
|
|
681
|
+
import { ServerWebSocket } from "bun";
|
|
682
|
+
|
|
683
|
+
@Catch(WsException)
|
|
684
|
+
class WsExceptionsFilter implements WsExceptionFilter {
|
|
685
|
+
catch(exception: WsException, host: ArgumentsHost) {
|
|
686
|
+
const client = host.switchToWs().getClient<ServerWebSocket>();
|
|
687
|
+
const error = exception.getError();
|
|
688
|
+
const details = typeof error === "object" ? error : { message: error };
|
|
689
|
+
|
|
690
|
+
client.send(
|
|
691
|
+
JSON.stringify({
|
|
692
|
+
event: "error",
|
|
693
|
+
data: {
|
|
694
|
+
message: "An error occurred",
|
|
695
|
+
details,
|
|
696
|
+
},
|
|
697
|
+
}),
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
@WebSocketGateway()
|
|
703
|
+
@UseFilters(WsExceptionsFilter)
|
|
704
|
+
class ErrorHandlingGateway {
|
|
705
|
+
@SubscribeMessage("risky")
|
|
706
|
+
handleRisky(@MessageBody() data: { shouldFail: boolean }) {
|
|
707
|
+
if (data.shouldFail) {
|
|
708
|
+
throw new WsException("Something went wrong");
|
|
709
|
+
}
|
|
710
|
+
return { event: "success", data: "OK" };
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
#### Broadcasting Messages
|
|
716
|
+
|
|
717
|
+
Broadcast messages to all connected clients using Bun's publish/subscribe system:
|
|
718
|
+
|
|
719
|
+
```ts
|
|
720
|
+
import {
|
|
721
|
+
WebSocketGateway,
|
|
722
|
+
WebSocketServer,
|
|
723
|
+
SubscribeMessage,
|
|
724
|
+
MessageBody,
|
|
725
|
+
ConnectedSocket,
|
|
726
|
+
OnGatewayConnection,
|
|
727
|
+
} from "@nestjs/websockets";
|
|
728
|
+
import { ServerWebSocket } from "bun";
|
|
729
|
+
import { BunPreflightHttpServer } from "@krisanalfa/bunest-adapter";
|
|
730
|
+
|
|
731
|
+
@Injectable() // Mandatory to be able to inject `BunPreflightHttpServer`
|
|
732
|
+
@WebSocketGateway()
|
|
733
|
+
class BroadcastGateway implements OnGatewayConnection {
|
|
734
|
+
@WebSocketServer()
|
|
735
|
+
server!: BunPreflightHttpServer; // Inject BunPreflightHttpServer
|
|
736
|
+
|
|
737
|
+
private readonly roomName = "global-room";
|
|
738
|
+
|
|
739
|
+
handleConnection(client: ServerWebSocket) {
|
|
740
|
+
// Subscribe client to room
|
|
741
|
+
client.subscribe(this.roomName);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
@SubscribeMessage("broadcast")
|
|
745
|
+
handleBroadcast(
|
|
746
|
+
@MessageBody() message: string,
|
|
747
|
+
@ConnectedSocket() socket: ServerWebSocket,
|
|
748
|
+
) {
|
|
749
|
+
// Get subscriber count
|
|
750
|
+
const count = this.server.getBunServer().subscriberCount(this.roomName);
|
|
751
|
+
|
|
752
|
+
// Publish to all subscribers in the room
|
|
753
|
+
socket.publishText(
|
|
754
|
+
this.roomName,
|
|
755
|
+
JSON.stringify({
|
|
756
|
+
event: "broadcast",
|
|
757
|
+
data: message,
|
|
758
|
+
subscribers: count,
|
|
759
|
+
}),
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**Key Features:**
|
|
766
|
+
|
|
767
|
+
- **Native Performance** - Uses Bun's native WebSocket implementation for maximum speed
|
|
768
|
+
- **NestJS Integration** - Full support for decorators, guards, pipes, and exception filters
|
|
769
|
+
- **Pub/Sub Support** - Built-in support for broadcasting messages to multiple clients
|
|
770
|
+
- **CORS Configuration** - Easy CORS setup for WebSocket connections
|
|
771
|
+
- **HTTP + WebSocket** - Run both HTTP and WebSocket servers on the same port
|
|
772
|
+
|
|
773
|
+
#### Secure WebSocket (WSS)
|
|
774
|
+
|
|
775
|
+
The Bun adapter supports secure WebSocket connections (WSS) using TLS/SSL certificates. You can configure WSS in two ways:
|
|
776
|
+
|
|
777
|
+
**Using BunAdapter constructor options:**
|
|
778
|
+
|
|
779
|
+
```ts
|
|
780
|
+
import { BunAdapter, BunWsAdapter } from "@krisanalfa/bunest-adapter";
|
|
781
|
+
import { NestFactory } from "@nestjs/core";
|
|
782
|
+
|
|
783
|
+
const app = await NestFactory.create(
|
|
784
|
+
AppModule,
|
|
785
|
+
new BunAdapter({
|
|
786
|
+
tls: {
|
|
787
|
+
cert: Bun.file("/path/to/cert.pem"),
|
|
788
|
+
key: Bun.file("/path/to/key.pem"),
|
|
789
|
+
},
|
|
790
|
+
}),
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
app.useWebSocketAdapter(new BunWsAdapter(app));
|
|
794
|
+
await app.listen(3000);
|
|
795
|
+
|
|
796
|
+
// Clients connect using wss:// protocol
|
|
797
|
+
// const ws = new WebSocket('wss://localhost:3000');
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
**Using NestFactory.create httpsOptions:**
|
|
801
|
+
|
|
802
|
+
```ts
|
|
803
|
+
import { BunAdapter, BunWsAdapter } from "@krisanalfa/bunest-adapter";
|
|
804
|
+
import { NestFactory } from "@nestjs/core";
|
|
805
|
+
|
|
806
|
+
const app = await NestFactory.create(AppModule, new BunAdapter(), {
|
|
807
|
+
httpsOptions: {
|
|
808
|
+
cert: Bun.file("/path/to/cert.pem"),
|
|
809
|
+
key: Bun.file("/path/to/key.pem"),
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
app.useWebSocketAdapter(new BunWsAdapter(app));
|
|
814
|
+
await app.listen(3000);
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
**Unix Socket Support with WSS:**
|
|
818
|
+
|
|
819
|
+
You can also run secure WebSocket servers over Unix sockets:
|
|
820
|
+
|
|
821
|
+
```ts
|
|
822
|
+
import { BunAdapter, BunWsAdapter } from "@krisanalfa/bunest-adapter";
|
|
823
|
+
import { NestFactory } from "@nestjs/core";
|
|
824
|
+
|
|
825
|
+
const app = await NestFactory.create(
|
|
826
|
+
AppModule,
|
|
827
|
+
new BunAdapter({
|
|
828
|
+
tls: {
|
|
829
|
+
cert: Bun.file("/path/to/cert.pem"),
|
|
830
|
+
key: Bun.file("/path/to/key.pem"),
|
|
831
|
+
},
|
|
832
|
+
}),
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
app.useWebSocketAdapter(new BunWsAdapter(app));
|
|
836
|
+
await app.listen("/tmp/secure-nestjs.sock");
|
|
837
|
+
|
|
838
|
+
// Or use abstract namespace socket on Linux
|
|
839
|
+
await app.listen("\0secure-nestjs-socket");
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
**Client Connection Example:**
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
// For development with self-signed certificates
|
|
846
|
+
const ws = new WebSocket("wss://localhost:3000", {
|
|
847
|
+
tls: { rejectUnauthorized: false },
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
ws.onopen = () => {
|
|
851
|
+
console.log("Connected to secure WebSocket");
|
|
852
|
+
ws.send(JSON.stringify({ event: "message", data: "Hello WSS!" }));
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
ws.onmessage = (event) => {
|
|
856
|
+
const data = JSON.parse(event.data);
|
|
857
|
+
console.log("Received:", data);
|
|
858
|
+
};
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**Important Notes:**
|
|
862
|
+
|
|
863
|
+
- WSS automatically uses the same port as your HTTPS server
|
|
864
|
+
- The `wss://` protocol is used instead of `ws://` for secure connections
|
|
865
|
+
- For production, use properly signed certificates from a trusted Certificate Authority
|
|
866
|
+
- For development, you can use self-signed certificates with `rejectUnauthorized: false` on the client
|
|
867
|
+
|
|
868
|
+
#### Limitations
|
|
869
|
+
|
|
870
|
+
**Port Configuration:** The WebSocket server port will always be the same as the Bun HTTP server port. In standard NestJS, you can configure different ports via the `@WebSocketGateway` decorator's `port` option (see [NestJS WebSocket documentation](https://docs.nestjs.com/websockets/gateways#overview)), but with the Bun adapter, WebSocket connections must use the same port as your HTTP server. This is due to Bun's unified server architecture where HTTP and WebSocket upgrades are handled by the same server instance.
|
|
871
|
+
|
|
872
|
+
```ts
|
|
873
|
+
// This port option is ignored with BunWsAdapter
|
|
874
|
+
@WebSocketGateway({ port: 8080 }) // ⚠️ Port option has no effect
|
|
875
|
+
class ChatGateway {
|
|
876
|
+
// Gateway will use the same port as app.listen()
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// WebSocket will be available on the same port as HTTP
|
|
880
|
+
const app = await NestFactory.create(AppModule, new BunAdapter());
|
|
881
|
+
app.useWebSocketAdapter(new BunWsAdapter(app));
|
|
882
|
+
await app.listen(3000); // Both HTTP and WebSocket use port 3000
|
|
883
|
+
```
|
|
884
|
+
|
|
445
885
|
### Bun File API Support
|
|
446
886
|
|
|
447
887
|
This package provides first-class support for Bun's native [`BunFile`](https://bun.com/docs/runtime/file-io) API, enabling seamless file uploads and downloads using Bun's efficient file handling capabilities.
|
|
@@ -550,7 +990,7 @@ The Bun adapter is developed with high code quality standards, including:
|
|
|
550
990
|
- Strict TypeScript typings
|
|
551
991
|
- Strict linting rules (ESLint)
|
|
552
992
|
- Comprehensive unit and integration tests
|
|
553
|
-
- Coverage reports (>
|
|
993
|
+
- Coverage reports (>97% line coverage, >85% function coverage)
|
|
554
994
|
|
|
555
995
|
## Request / Response Objects
|
|
556
996
|
|
|
@@ -853,38 +1293,102 @@ class HybridController {
|
|
|
853
1293
|
|
|
854
1294
|
## Benchmark Results
|
|
855
1295
|
|
|
856
|
-
Tested on MacOS Sequoia (15.6.1), Apple M1 Max (64GB RAM), Bun 1.3.5, Node.js 20.10.0
|
|
1296
|
+
Tested on MacOS Sequoia (15.6.1), Apple M1 Max (64GB RAM), Bun 1.3.5, Node.js 20.10.0.
|
|
1297
|
+
|
|
1298
|
+
### HTTP Benchmark
|
|
1299
|
+
|
|
1300
|
+
HTTP benchmarks run using [`oha`](https://github.com/hatoo/oha) tool with the following command:
|
|
1301
|
+
|
|
1302
|
+
```
|
|
1303
|
+
oha -c 125 -n 1000000 --no-tui "http://127.0.0.1:3000/"
|
|
1304
|
+
```
|
|
857
1305
|
|
|
858
1306
|
| Configuration | Requests/sec | Compared to Pure Bun |
|
|
859
1307
|
| --------------------------------------------------------------------------------- | -----------: | -------------------: |
|
|
860
1308
|
| Pure Bun | 80,742.72 | 100.00% |
|
|
861
|
-
| Nest + Bun + Native Bun Adapter |
|
|
1309
|
+
| Nest + Bun + Native Bun Adapter | 70,234.76 | 86.98% |
|
|
862
1310
|
| Nest + Bun + Express Adapter | 43,375.97 | 53.72% |
|
|
863
1311
|
| Nest + Bun + [Hono Adapter](https://www.npmjs.com/package/@kiyasov/platform-hono) | 19,194.78 | 23.77% |
|
|
864
1312
|
| Nest + Node + Express | 14,019.88 | 17.36% |
|
|
865
1313
|
|
|
866
1314
|
> **Pure Bun** is the fastest at **80,743 req/s**. **Nest + Bun + Native Bun Adapter** achieves **~86%** of Pure Bun's performance while providing full NestJS features, and is **~5x faster** than Nest + Node + Express. Compared to Bun with Express adapter, the native Bun adapter is **~1.6x faster**.
|
|
867
1315
|
|
|
868
|
-
|
|
1316
|
+
Bonus if you use Unix sockets:
|
|
1317
|
+
```
|
|
1318
|
+
Summary:
|
|
1319
|
+
Success rate: 100.00%
|
|
1320
|
+
Total: 8298.2243 ms
|
|
1321
|
+
Slowest: 5.2326 ms
|
|
1322
|
+
Fastest: 0.2857 ms
|
|
1323
|
+
Average: 1.0361 ms
|
|
1324
|
+
Requests/sec: 120507.7092
|
|
1325
|
+
|
|
1326
|
+
Total data: 21.93 MiB
|
|
1327
|
+
Size/request: 23 B
|
|
1328
|
+
Size/sec: 2.64 MiB
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
As you can see, using Unix sockets boosts the performance further to **120,508 req/s**, which is **~1.5x faster** than TCP. Since Bun `fetch` supports Unix sockets, you can leverage this for inter-process communication on the same machine.
|
|
1332
|
+
|
|
1333
|
+
### WebSocket Benchmark
|
|
1334
|
+
|
|
1335
|
+
WebSocket benchmarks run using the custom benchmark script in `benchmarks/ws.benchmark.ts`.
|
|
1336
|
+
|
|
1337
|
+
| Configuration | Messages/sec | Compared to Pure Bun |
|
|
1338
|
+
| ---------------------------------- | -----------: | -------------------: |
|
|
1339
|
+
| Pure Bun WebSocket | 817,594.60 | 100.00% |
|
|
1340
|
+
| Nest + Bun + BunWsAdapter | 764,962.70 | 93.56% |
|
|
1341
|
+
| Nest + Bun + WebSocketAdapter (ws) | 299,161.50 | 36.59% |
|
|
1342
|
+
|
|
1343
|
+
> **Pure Bun WebSocket** achieves **817,595 msg/s**. **Nest + Bun + BunWsAdapter** achieves **~94%** of Pure Bun's performance, and is **~2.6x faster** than using the standard WebSocketAdapter with `ws` library.
|
|
1344
|
+
|
|
1345
|
+
### Running HTTP Benchmark
|
|
869
1346
|
|
|
870
1347
|
This project includes benchmark configurations in the `benchmarks` directory.
|
|
871
|
-
To run the specific server benchmark, you can use predefined scripts in `package.json
|
|
1348
|
+
To run the specific HTTP server benchmark, you can use predefined scripts in `package.json`:
|
|
872
1349
|
|
|
873
1350
|
```bash
|
|
874
1351
|
# Running native bun server benchmark
|
|
875
|
-
bun run native
|
|
1352
|
+
bun run http:native
|
|
876
1353
|
|
|
877
1354
|
# Running NestJS with Bun adapter benchmark
|
|
878
|
-
bun run bun
|
|
1355
|
+
bun run http:bun
|
|
879
1356
|
|
|
880
1357
|
# Running NestJS with Hono adapter benchmark
|
|
881
|
-
bun run hono
|
|
1358
|
+
bun run http:hono
|
|
882
1359
|
|
|
883
1360
|
# Running NestJS with Express adapter benchmark
|
|
884
|
-
bun run express
|
|
1361
|
+
bun run http:express
|
|
885
1362
|
|
|
886
1363
|
# Running NestJS with Node and Express benchmark
|
|
887
|
-
bun run node
|
|
1364
|
+
bun run http:node
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
Then run the benchmark using [`oha`](https://github.com/hatoo/oha):
|
|
1368
|
+
|
|
1369
|
+
```bash
|
|
1370
|
+
oha -c 125 -n 1000000 --no-tui "http://127.0.0.1:3000/"
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
### Running WebSocket Benchmark
|
|
1374
|
+
|
|
1375
|
+
To run WebSocket benchmarks, first start the WebSocket server:
|
|
1376
|
+
|
|
1377
|
+
```bash
|
|
1378
|
+
# Running native bun websocket benchmark
|
|
1379
|
+
bun run ws:native
|
|
1380
|
+
|
|
1381
|
+
# Running NestJS with BunWsAdapter websocket benchmark
|
|
1382
|
+
bun run ws:bun
|
|
1383
|
+
|
|
1384
|
+
# Running NestJS with WebSocketAdapter (ws) websocket benchmark
|
|
1385
|
+
bun run ws:ws
|
|
1386
|
+
```
|
|
1387
|
+
|
|
1388
|
+
Then run the benchmark script:
|
|
1389
|
+
|
|
1390
|
+
```bash
|
|
1391
|
+
bun benchmarks/ws.benchmark.ts
|
|
888
1392
|
```
|
|
889
1393
|
|
|
890
1394
|
All benchmarks use port `3000` by default. You can adjust the port in the respective benchmark files if needed.
|
|
@@ -895,9 +1399,8 @@ Contributions are welcome! Please open issues or submit pull requests for bug fi
|
|
|
895
1399
|
|
|
896
1400
|
## Future Plans
|
|
897
1401
|
|
|
898
|
-
- Support for WebSocket integration with Bun
|
|
899
1402
|
- Enhanced trusted proxy configuration for host header handling
|
|
900
|
-
-
|
|
1403
|
+
- Improved documentation and examples
|
|
901
1404
|
- Release automation via CI/CD pipelines
|
|
902
1405
|
|
|
903
1406
|
## License
|
package/dist/bun.adapter.d.ts
CHANGED
|
@@ -1,58 +1,24 @@
|
|
|
1
1
|
import { CorsOptions, CorsOptionsDelegate } from '@nestjs/common/interfaces/external/cors-options.interface.js';
|
|
2
2
|
import { ErrorHandler, RequestHandler } from '@nestjs/common/interfaces/index.js';
|
|
3
3
|
import { NestApplicationOptions, RequestMethod, VersioningOptions } from '@nestjs/common';
|
|
4
|
-
import {
|
|
4
|
+
import { Server } from 'bun';
|
|
5
5
|
import { AbstractHttpAdapter } from '@nestjs/core';
|
|
6
6
|
import { VersionValue } from '@nestjs/common/interfaces/version-options.interface.js';
|
|
7
|
+
import { BunWsClientData, ServerOptions } from './bun.internal.types.js';
|
|
8
|
+
import { BunPreflightHttpServer } from './bun.preflight-http-server.js';
|
|
7
9
|
import { BunRequest } from './bun.request.js';
|
|
8
10
|
import { BunResponse } from './bun.response.js';
|
|
11
|
+
import { BunServerInstance } from './bun.server-instance.js';
|
|
9
12
|
export declare class BunAdapter extends AbstractHttpAdapter<Server<unknown>, BunRequest, BunResponse> {
|
|
10
|
-
|
|
13
|
+
protected bunServeOptions: ServerOptions<BunWsClientData>;
|
|
11
14
|
private readonly logger;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
private readonly routes;
|
|
15
|
-
private readonly routeHandlers;
|
|
16
|
-
private notFoundHandler;
|
|
17
|
-
constructor(bunServeOptions?: Pick<Serve.Options<unknown>, 'development' | 'maxRequestBodySize' | 'idleTimeout' | 'id' | 'tls'>);
|
|
18
|
-
use(middleware: RequestHandler<BunRequest, BunResponse>): void;
|
|
19
|
-
get(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
20
|
-
get(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
21
|
-
post(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
22
|
-
post(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
23
|
-
put(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
24
|
-
put(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
25
|
-
patch(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
26
|
-
patch(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
27
|
-
delete(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
28
|
-
delete(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
29
|
-
head(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
30
|
-
head(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
31
|
-
options(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
32
|
-
options(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
33
|
-
all(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
34
|
-
all(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
35
|
-
propfind(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
36
|
-
propfind(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
37
|
-
proppatch(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
38
|
-
proppatch(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
39
|
-
mkcol(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
40
|
-
mkcol(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
41
|
-
copy(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
42
|
-
copy(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
43
|
-
move(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
44
|
-
move(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
45
|
-
lock(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
46
|
-
lock(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
47
|
-
unlock(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
48
|
-
unlock(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
49
|
-
search(handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
50
|
-
search(path: unknown, handler: RequestHandler<BunRequest, BunResponse>): void;
|
|
15
|
+
protected instance: BunServerInstance;
|
|
16
|
+
constructor(bunServeOptions?: ServerOptions<BunWsClientData>);
|
|
51
17
|
useStaticAssets(...args: unknown[]): void;
|
|
52
18
|
setViewEngine(engine: string): void;
|
|
53
19
|
render(response: unknown, view: string, options: unknown): void;
|
|
54
20
|
close(): Promise<void>;
|
|
55
|
-
initHttpServer(options: NestApplicationOptions):
|
|
21
|
+
initHttpServer(options: NestApplicationOptions): BunPreflightHttpServer;
|
|
56
22
|
getRequestHostname(request: BunRequest): string;
|
|
57
23
|
getRequestMethod(request: BunRequest): string;
|
|
58
24
|
getRequestUrl(request: BunRequest): string;
|
|
@@ -84,10 +50,5 @@ export declare class BunAdapter extends AbstractHttpAdapter<Server<unknown>, Bun
|
|
|
84
50
|
* @param callback Optional callback to invoke once the server is listening.
|
|
85
51
|
*/
|
|
86
52
|
listen(port: string | number, hostname: string, callback?: () => void): void;
|
|
87
|
-
private
|
|
88
|
-
private createVersioningHandlers;
|
|
89
|
-
private executeHandlerChain;
|
|
90
|
-
private createChainedHandlerForVersioningResolution;
|
|
91
|
-
private mapRequestMethodToString;
|
|
92
|
-
private parseRouteHandler;
|
|
53
|
+
private configureTls;
|
|
93
54
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CorsOptionsDelegate, CorsOptions as NestCorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface.js';
|
|
2
|
+
import { Serve, Server, ServerWebSocket, WebSocketHandler } from 'bun';
|
|
3
|
+
import { BunRequest } from './bun.request.js';
|
|
4
|
+
export interface WsOptions extends Pick<WebSocketHandler<unknown>, 'maxPayloadLength' | 'idleTimeout' | 'backpressureLimit' | 'closeOnBackpressureLimit' | 'sendPings' | 'publishToSelf' | 'perMessageDeflate'> {
|
|
5
|
+
cors?: true | NestCorsOptions | CorsOptionsDelegate<BunRequest>;
|
|
6
|
+
clientDataFactory?: (req: BunRequest) => unknown;
|
|
7
|
+
}
|
|
8
|
+
export type ServerOptions<TWebSocketData = unknown> = Pick<Serve.Options<TWebSocketData>, 'development' | 'maxRequestBodySize' | 'idleTimeout' | 'id' | 'tls' | 'websocket' | 'port' | 'hostname'>;
|
|
9
|
+
export type WsData = string | Buffer | ArrayBuffer | Buffer[];
|
|
10
|
+
export interface WsHandlers {
|
|
11
|
+
onOpen: ((ws: ServerWebSocket<unknown>) => void) | undefined;
|
|
12
|
+
onMessage: ((ws: ServerWebSocket<unknown>, message: WsData, server: Server<unknown>) => void) | undefined;
|
|
13
|
+
onClose: ((ws: ServerWebSocket<unknown>, code: number, reason: string) => void) | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface BunWsClientData {
|
|
16
|
+
/** Called when a message is received - matches bun.adapter.ts onMessageInternal */
|
|
17
|
+
onMessageInternal?: (message: WsData) => void;
|
|
18
|
+
/** Called when the connection closes - matches bun.adapter.ts onCloseInternal */
|
|
19
|
+
onCloseInternal?: () => void;
|
|
20
|
+
/** Called by NestJS for disconnect handling */
|
|
21
|
+
onDisconnect?: (ws: ServerWebSocket<unknown>) => void;
|
|
22
|
+
}
|