@krisanalfa/bunest-adapter 0.4.0 → 0.5.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 +76 -17
- package/dist/bun.adapter.d.ts +2 -2
- package/dist/bun.internal.types.d.ts +33 -2
- package/dist/bun.request.d.ts +18 -9
- package/dist/bun.response.d.ts +7 -0
- package/dist/bun.server-instance.d.ts +8 -2
- package/dist/index.js +102 -15
- package/dist/index.js.map +6 -6
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ This project provides a native Bun adapter for NestJS, allowing developers to le
|
|
|
20
20
|
- [CORS](#cors)
|
|
21
21
|
- [Cookies](#cookies)
|
|
22
22
|
- [Popular Express Middleware](#popular-express-middleware)
|
|
23
|
+
- [Static Assets](#static-assets)
|
|
23
24
|
- [Bun File API Support](#bun-file-api-support)
|
|
24
25
|
- [BunFileInterceptor](#bunfileinterceptor)
|
|
25
26
|
- [WebSocket Support](#websocket-support)
|
|
@@ -51,7 +52,7 @@ This project provides a native Bun adapter for NestJS, allowing developers to le
|
|
|
51
52
|
Easy to set up and use Bun as the underlying HTTP server for your NestJS applications.
|
|
52
53
|
|
|
53
54
|
```ts
|
|
54
|
-
import { BunAdapter } from "@krisanalfa/bunest-adapter";
|
|
55
|
+
import { BunAdapter, NestBunApplication } from "@krisanalfa/bunest-adapter";
|
|
55
56
|
import { Logger } from "@nestjs/common";
|
|
56
57
|
import { NestFactory } from "@nestjs/core";
|
|
57
58
|
import { Server } from "bun";
|
|
@@ -59,10 +60,10 @@ import { Server } from "bun";
|
|
|
59
60
|
import { AppModule } from "./app.module.js";
|
|
60
61
|
|
|
61
62
|
async function main() {
|
|
62
|
-
const app = await NestFactory.create(AppModule, new BunAdapter());
|
|
63
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter());
|
|
63
64
|
await app.listen(3000);
|
|
64
|
-
const server = app.
|
|
65
|
-
Logger.log(`Server started on ${server
|
|
65
|
+
const server = app.getHttpServer().getBunServer()
|
|
66
|
+
Logger.log(`Server started on ${server?.url.toString() ?? 'http://localhost:3000'}`, 'NestApplication')
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
await main();
|
|
@@ -495,7 +496,7 @@ app.enableCors((req: BunRequest, callback) => {
|
|
|
495
496
|
You can also use NestJS's `CorsOptions` type for static configuration.
|
|
496
497
|
|
|
497
498
|
```ts
|
|
498
|
-
const app = await NestFactory.create(AppModule, new BunAdapter(), {
|
|
499
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter(), {
|
|
499
500
|
cors: {
|
|
500
501
|
origin: "https://example.com",
|
|
501
502
|
methods: ["GET", "POST", "PUT"],
|
|
@@ -535,7 +536,7 @@ Compatible with popular Express middleware:
|
|
|
535
536
|
```ts
|
|
536
537
|
import helmet from "helmet";
|
|
537
538
|
|
|
538
|
-
const app = await NestFactory.create(AppModule, new BunAdapter());
|
|
539
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter());
|
|
539
540
|
app.use(helmet());
|
|
540
541
|
```
|
|
541
542
|
|
|
@@ -543,7 +544,65 @@ Tested and working with:
|
|
|
543
544
|
|
|
544
545
|
- `helmet` - Security headers
|
|
545
546
|
- `cors` - CORS handling
|
|
546
|
-
-
|
|
547
|
+
- `@thallesp/nestjs-better-auth` - `better-auth` middleware
|
|
548
|
+
- `express-session` - Session management
|
|
549
|
+
|
|
550
|
+
#### Static Assets
|
|
551
|
+
|
|
552
|
+
Serve static files from your NestJS application using Bun's native file serving capabilities. The adapter supports two distinct modes:
|
|
553
|
+
|
|
554
|
+
**File Routes (Default)** - Reads files from the filesystem on each request, supports range requests, respects middlewares, and provides full HTTP feature compatibility:
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
import { join } from 'path';
|
|
558
|
+
|
|
559
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter());
|
|
560
|
+
|
|
561
|
+
// Serve static assets using file routes (default)
|
|
562
|
+
app.useStaticAssets(join(__dirname, 'public'));
|
|
563
|
+
|
|
564
|
+
// Or explicitly set useStatic to false
|
|
565
|
+
app.useStaticAssets(join(__dirname, 'public'), { useStatic: false });
|
|
566
|
+
|
|
567
|
+
await app.listen(3000);
|
|
568
|
+
|
|
569
|
+
// Files in 'public' directory are now accessible:
|
|
570
|
+
// public/index.html -> http://localhost:3000/index.html
|
|
571
|
+
// public/css/style.css -> http://localhost:3000/css/style.css
|
|
572
|
+
// public/images/logo.png -> http://localhost:3000/images/logo.png
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
> Note: Even if the file routes read the files from the filesystem on each request, you still need to make sure that Bun has access to the files right before you call `app.listen()`. The adapter reads the directory structure and prepares the routes at that time. If you need different behavior (e.g., dynamic files), consider using a custom controller to serve those files.
|
|
576
|
+
|
|
577
|
+
**Static Routes** - Serves files directly from memory for maximum performance, but with some limitations (no range requests, doesn't respect middlewares):
|
|
578
|
+
|
|
579
|
+
```ts
|
|
580
|
+
import { join } from 'path';
|
|
581
|
+
|
|
582
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter());
|
|
583
|
+
|
|
584
|
+
// Serve static assets using static routes (faster, but with limitations)
|
|
585
|
+
app.useStaticAssets(join(__dirname, 'public'), { useStatic: true });
|
|
586
|
+
|
|
587
|
+
await app.listen(3000);
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**With CORS Support:**
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
const app = await NestFactory.create<NestBunApplication>(
|
|
594
|
+
AppModule,
|
|
595
|
+
new BunAdapter(),
|
|
596
|
+
{ cors: true }
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
// Static assets will respect CORS settings when using file routes
|
|
600
|
+
app.useStaticAssets(join(__dirname, 'public'), { useStatic: false });
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Choosing Between Modes:**
|
|
604
|
+
|
|
605
|
+
For more details, see [Bun's documentation on file responses vs static responses](https://bun.sh/docs/runtime/http/routing#file-responses-vs-static-responses).
|
|
547
606
|
|
|
548
607
|
### WebSocket Support
|
|
549
608
|
|
|
@@ -585,7 +644,7 @@ class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|
|
585
644
|
}
|
|
586
645
|
|
|
587
646
|
// Enable WebSocket support in your application
|
|
588
|
-
const app = await NestFactory.create(AppModule, new BunAdapter());
|
|
647
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter());
|
|
589
648
|
app.useWebSocketAdapter(new BunWsAdapter(app));
|
|
590
649
|
await app.listen(3000);
|
|
591
650
|
```
|
|
@@ -777,10 +836,10 @@ The Bun adapter supports secure WebSocket connections (WSS) using TLS/SSL certif
|
|
|
777
836
|
**Using BunAdapter constructor options:**
|
|
778
837
|
|
|
779
838
|
```ts
|
|
780
|
-
import { BunAdapter, BunWsAdapter } from "@krisanalfa/bunest-adapter";
|
|
839
|
+
import { BunAdapter, BunWsAdapter, NestBunApplication } from "@krisanalfa/bunest-adapter";
|
|
781
840
|
import { NestFactory } from "@nestjs/core";
|
|
782
841
|
|
|
783
|
-
const app = await NestFactory.create(
|
|
842
|
+
const app = await NestFactory.create<NestBunApplication>(
|
|
784
843
|
AppModule,
|
|
785
844
|
new BunAdapter({
|
|
786
845
|
tls: {
|
|
@@ -800,10 +859,10 @@ await app.listen(3000);
|
|
|
800
859
|
**Using NestFactory.create httpsOptions:**
|
|
801
860
|
|
|
802
861
|
```ts
|
|
803
|
-
import { BunAdapter, BunWsAdapter } from "@krisanalfa/bunest-adapter";
|
|
862
|
+
import { BunAdapter, BunWsAdapter, NestBunApplication } from "@krisanalfa/bunest-adapter";
|
|
804
863
|
import { NestFactory } from "@nestjs/core";
|
|
805
864
|
|
|
806
|
-
const app = await NestFactory.create(AppModule, new BunAdapter(), {
|
|
865
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter(), {
|
|
807
866
|
httpsOptions: {
|
|
808
867
|
cert: Bun.file("/path/to/cert.pem"),
|
|
809
868
|
key: Bun.file("/path/to/key.pem"),
|
|
@@ -819,10 +878,10 @@ await app.listen(3000);
|
|
|
819
878
|
You can also run secure WebSocket servers over Unix sockets:
|
|
820
879
|
|
|
821
880
|
```ts
|
|
822
|
-
import { BunAdapter, BunWsAdapter } from "@krisanalfa/bunest-adapter";
|
|
881
|
+
import { BunAdapter, BunWsAdapter, NestBunApplication } from "@krisanalfa/bunest-adapter";
|
|
823
882
|
import { NestFactory } from "@nestjs/core";
|
|
824
883
|
|
|
825
|
-
const app = await NestFactory.create(
|
|
884
|
+
const app = await NestFactory.create<NestBunApplication>(
|
|
826
885
|
AppModule,
|
|
827
886
|
new BunAdapter({
|
|
828
887
|
tls: {
|
|
@@ -877,7 +936,7 @@ class ChatGateway {
|
|
|
877
936
|
}
|
|
878
937
|
|
|
879
938
|
// WebSocket will be available on the same port as HTTP
|
|
880
|
-
const app = await NestFactory.create(AppModule, new BunAdapter());
|
|
939
|
+
const app = await NestFactory.create<NestBunApplication>(AppModule, new BunAdapter());
|
|
881
940
|
app.useWebSocketAdapter(new BunWsAdapter(app));
|
|
882
941
|
await app.listen(3000); // Both HTTP and WebSocket use port 3000
|
|
883
942
|
```
|
|
@@ -943,7 +1002,7 @@ You can run your NestJS application with HTTPS using two approaches:
|
|
|
943
1002
|
#### Using Bun's built-in HTTPS support (recommended)
|
|
944
1003
|
|
|
945
1004
|
```ts
|
|
946
|
-
const app = await NestFactory.create(
|
|
1005
|
+
const app = await NestFactory.create<NestBunApplication>(
|
|
947
1006
|
AppModule,
|
|
948
1007
|
new BunAdapter({
|
|
949
1008
|
tls: {
|
|
@@ -957,7 +1016,7 @@ const app = await NestFactory.create(
|
|
|
957
1016
|
#### Using NestJS App Factory HTTPS options
|
|
958
1017
|
|
|
959
1018
|
```ts
|
|
960
|
-
const app = await NestFactory.create(
|
|
1019
|
+
const app = await NestFactory.create<NestBunApplication>(
|
|
961
1020
|
AppModule,
|
|
962
1021
|
new BunAdapter(/* leave it empty */),
|
|
963
1022
|
{
|
package/dist/bun.adapter.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { NestApplicationOptions, RequestMethod, VersioningOptions } from '@nestj
|
|
|
4
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';
|
|
7
|
+
import { BunStaticAssetsOptions, BunWsClientData, ServerOptions } from './bun.internal.types.js';
|
|
8
8
|
import { BunPreflightHttpServer } from './bun.preflight-http-server.js';
|
|
9
9
|
import { BunRequest } from './bun.request.js';
|
|
10
10
|
import { BunResponse } from './bun.response.js';
|
|
@@ -14,7 +14,7 @@ export declare class BunAdapter extends AbstractHttpAdapter<Server<unknown>, Bun
|
|
|
14
14
|
private readonly logger;
|
|
15
15
|
protected instance: BunServerInstance;
|
|
16
16
|
constructor(bunServeOptions?: ServerOptions<BunWsClientData>);
|
|
17
|
-
useStaticAssets(
|
|
17
|
+
useStaticAssets(path: string, options?: BunStaticAssetsOptions): void;
|
|
18
18
|
setViewEngine(engine: string): void;
|
|
19
19
|
render(response: unknown, view: string, options: unknown): void;
|
|
20
20
|
close(): Promise<void>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CorsOptions, CorsOptionsDelegate } from '@nestjs/common/interfaces/external/cors-options.interface.js';
|
|
2
2
|
import { Serve, Server, ServerWebSocket, WebSocketHandler } from 'bun';
|
|
3
|
+
import { INestApplication } from '@nestjs/common';
|
|
4
|
+
import { BunPreflightHttpServer } from './bun.preflight-http-server.js';
|
|
3
5
|
import { BunRequest } from './bun.request.js';
|
|
4
6
|
export interface WsOptions extends Pick<WebSocketHandler<unknown>, 'maxPayloadLength' | 'idleTimeout' | 'backpressureLimit' | 'closeOnBackpressureLimit' | 'sendPings' | 'publishToSelf' | 'perMessageDeflate'> {
|
|
5
|
-
cors?: true |
|
|
7
|
+
cors?: true | CorsOptions | CorsOptionsDelegate<BunRequest>;
|
|
6
8
|
clientDataFactory?: (req: BunRequest) => unknown;
|
|
7
9
|
}
|
|
8
10
|
export type ServerOptions<TWebSocketData = unknown> = Pick<Serve.Options<TWebSocketData>, 'development' | 'maxRequestBodySize' | 'idleTimeout' | 'id' | 'tls' | 'websocket' | 'port' | 'hostname'>;
|
|
@@ -20,3 +22,32 @@ export interface BunWsClientData {
|
|
|
20
22
|
/** Called by NestJS for disconnect handling */
|
|
21
23
|
onDisconnect?: (ws: ServerWebSocket<unknown>) => void;
|
|
22
24
|
}
|
|
25
|
+
export interface BunStaticAssetsOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Enable static assets serving.
|
|
28
|
+
*
|
|
29
|
+
* Bun has two distict modes for serving static assets:
|
|
30
|
+
* 1. Static routes
|
|
31
|
+
* 2. File routes
|
|
32
|
+
*
|
|
33
|
+
* If you set `useStatic: true`, Bun will use static routes for serving assets.
|
|
34
|
+
* This approach is generally faster for serving static files, as it serves
|
|
35
|
+
* files directly from memory. However, it comes with some limitations, such as
|
|
36
|
+
* lack of support for certain features like range requests and directory indexing.
|
|
37
|
+
* On top of that, static routes didn't respect middlewares due to Bun's internal design.
|
|
38
|
+
*
|
|
39
|
+
* On the other hand, if you set `useStatic: false` (the default behavior),
|
|
40
|
+
* Bun will use file routes, which read files from the filesystem on each request.
|
|
41
|
+
* This method supports a wider range of features, including range requests, and respects
|
|
42
|
+
* middlewares. However, it may be slightly slower than static routes due to
|
|
43
|
+
* filesystem access on each request.
|
|
44
|
+
*
|
|
45
|
+
* @see https://bun.com/docs/runtime/http/routing#file-responses-vs-static-responses
|
|
46
|
+
* @defaults false Use file routes by default.
|
|
47
|
+
*/
|
|
48
|
+
useStatic?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface NestBunApplication extends INestApplication<BunPreflightHttpServer> {
|
|
51
|
+
useStaticAssets(path: string, options?: BunStaticAssetsOptions): void;
|
|
52
|
+
enableCors(options?: CorsOptions | CorsOptionsDelegate<BunRequest>): void;
|
|
53
|
+
}
|
package/dist/bun.request.d.ts
CHANGED
|
@@ -3,6 +3,15 @@ import { ParsedQs } from 'qs';
|
|
|
3
3
|
type HeadersProxy = Record<string, string> & {
|
|
4
4
|
get: (key: string) => string | null;
|
|
5
5
|
};
|
|
6
|
+
interface Connection {
|
|
7
|
+
encrypted: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface TcpSocket {
|
|
10
|
+
encrypted: boolean;
|
|
11
|
+
setKeepAlive: (enable?: boolean, initialDelay?: number) => void;
|
|
12
|
+
setNoDelay: (noDelay?: boolean) => void;
|
|
13
|
+
setTimeout: (timeout: number, callback?: () => void) => void;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* A high-performance request wrapper for Bun's native request object.
|
|
8
17
|
* Provides lazy parsing and caching for optimal performance in NestJS applications.
|
|
@@ -26,23 +35,23 @@ export declare class BunRequest {
|
|
|
26
35
|
private _file;
|
|
27
36
|
private _files;
|
|
28
37
|
private _settings;
|
|
29
|
-
private
|
|
38
|
+
private _connection;
|
|
39
|
+
private _socket;
|
|
40
|
+
private _url;
|
|
30
41
|
private readonly _parsedUrl;
|
|
31
42
|
readonly method: string;
|
|
32
43
|
readonly params: Record<string, string>;
|
|
33
44
|
constructor(nativeRequest: NativeRequest);
|
|
45
|
+
/**
|
|
46
|
+
* Gets a mock connection object for compatibility with Node.js middleware.
|
|
47
|
+
* Some middleware (like express-session) check req.connection.encrypted to determine if the connection is HTTPS.
|
|
48
|
+
*/
|
|
49
|
+
get connection(): Connection;
|
|
34
50
|
/**
|
|
35
51
|
* Gets a mock socket object for compatibility with Node.js middleware.
|
|
36
52
|
* Some middleware (like Better Auth) check req.socket.encrypted to determine if the connection is HTTPS.
|
|
37
|
-
*
|
|
38
|
-
* @returns A mock socket object with encrypted property
|
|
39
53
|
*/
|
|
40
|
-
get socket():
|
|
41
|
-
encrypted: boolean;
|
|
42
|
-
setKeepAlive: () => void;
|
|
43
|
-
setNoDelay: () => void;
|
|
44
|
-
setTimeout: () => void;
|
|
45
|
-
};
|
|
54
|
+
get socket(): TcpSocket;
|
|
46
55
|
/**
|
|
47
56
|
* Gets the URL path and query string of the request.
|
|
48
57
|
* Returns the pathname + search params for Node.js/Express compatibility.
|
package/dist/bun.response.d.ts
CHANGED
|
@@ -373,6 +373,13 @@ export declare class BunResponse {
|
|
|
373
373
|
* ```
|
|
374
374
|
*/
|
|
375
375
|
isEnded(): boolean;
|
|
376
|
+
/**
|
|
377
|
+
* Stub method for Node.js HTTP response compatibility.
|
|
378
|
+
*
|
|
379
|
+
* The primary purpose of _implicitHeader() is to automatically generate and send the HTTP headers if a write operation (like `response.write()` or `response.end()`)
|
|
380
|
+
* is called without explicitly calling `response.writeHead()` first.
|
|
381
|
+
*/
|
|
382
|
+
_implicitHeader(): void;
|
|
376
383
|
private buildStreamableResponse;
|
|
377
384
|
private buildJsonResponse;
|
|
378
385
|
private createResponse;
|
|
@@ -2,7 +2,7 @@ import { CorsOptions, CorsOptionsDelegate } from '@nestjs/common/interfaces/exte
|
|
|
2
2
|
import { RequestMethod } from '@nestjs/common';
|
|
3
3
|
import { Server, ServerWebSocket } from 'bun';
|
|
4
4
|
import { RequestHandler } from '@nestjs/common/interfaces/index.js';
|
|
5
|
-
import { BunWsClientData, ServerOptions, WsData, WsOptions } from './bun.internal.types.js';
|
|
5
|
+
import { BunStaticAssetsOptions, BunWsClientData, ServerOptions, WsData, WsOptions } from './bun.internal.types.js';
|
|
6
6
|
import { BunMiddlewareEngine } from './bun.middleware-engine.js';
|
|
7
7
|
import { BunRequest } from './bun.request.js';
|
|
8
8
|
import { BunResponse } from './bun.response.js';
|
|
@@ -25,6 +25,7 @@ export declare class BunServerInstance {
|
|
|
25
25
|
private wsOptions;
|
|
26
26
|
private useWs;
|
|
27
27
|
private useWsCors;
|
|
28
|
+
private staticAssetsOptions;
|
|
28
29
|
private httpServer;
|
|
29
30
|
constructor(bunServeOptions: ServerOptions<BunWsClientData>);
|
|
30
31
|
use(maybePath: string | RequestHandler<BunRequest, BunResponse>, maybeHandler?: RequestHandler<BunRequest, BunResponse>): void;
|
|
@@ -52,7 +53,7 @@ export declare class BunServerInstance {
|
|
|
52
53
|
* @param hostnameOrCallback The hostname to bind to or the callback function.
|
|
53
54
|
* @param maybeCallback Optional callback to invoke once the server is listening.
|
|
54
55
|
*/
|
|
55
|
-
listen(port: string | number, hostnameOrCallback?: string | (() => void), maybeCallback?: () => void): Server<unknown
|
|
56
|
+
listen(port: string | number, hostnameOrCallback?: string | (() => void), maybeCallback?: () => void): Promise<Server<unknown>>;
|
|
56
57
|
/**
|
|
57
58
|
* NestJS compatibility methods - stop the server
|
|
58
59
|
*/
|
|
@@ -112,11 +113,16 @@ export declare class BunServerInstance {
|
|
|
112
113
|
* Enable CORS middleware
|
|
113
114
|
*/
|
|
114
115
|
enableCors(corsOptions?: CorsOptions | CorsOptionsDelegate<BunRequest>, prefix?: string): void;
|
|
116
|
+
/**
|
|
117
|
+
* Serve static assets
|
|
118
|
+
*/
|
|
119
|
+
useStaticAssets(path: string, options?: BunStaticAssetsOptions): void;
|
|
115
120
|
private static isNumericPort;
|
|
116
121
|
private static omitKeys;
|
|
117
122
|
private isWebSocketUpgradeRequest;
|
|
118
123
|
private provideCorsHeaders;
|
|
119
124
|
private upgradeWebSocket;
|
|
125
|
+
private setupStaticAssetsIfNeeded;
|
|
120
126
|
private setupWebSocketIfNeeded;
|
|
121
127
|
private createServer;
|
|
122
128
|
private delegateRouteHandler;
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,8 @@ class BunPreflightHttpServer {
|
|
|
58
58
|
|
|
59
59
|
// lib/bun.server-instance.ts
|
|
60
60
|
import { Logger } from "@nestjs/common";
|
|
61
|
+
import { join } from "path";
|
|
62
|
+
import { readdir } from "fs/promises";
|
|
61
63
|
|
|
62
64
|
// lib/bun.body-parser.middleware.ts
|
|
63
65
|
var GET_CODE = 71;
|
|
@@ -429,28 +431,34 @@ class BunRequest {
|
|
|
429
431
|
_file = null;
|
|
430
432
|
_files = null;
|
|
431
433
|
_settings = null;
|
|
432
|
-
|
|
434
|
+
_connection = null;
|
|
435
|
+
_socket = null;
|
|
436
|
+
_url = null;
|
|
433
437
|
_parsedUrl;
|
|
434
438
|
method;
|
|
435
439
|
params;
|
|
436
440
|
constructor(nativeRequest) {
|
|
437
441
|
this.nativeRequest = nativeRequest;
|
|
438
|
-
this.
|
|
439
|
-
this._parsedUrl = new URL(this._url);
|
|
442
|
+
this._parsedUrl = new URL(nativeRequest.url);
|
|
440
443
|
this._nativeHeaders = nativeRequest.headers;
|
|
441
444
|
this.method = nativeRequest.method;
|
|
442
445
|
this.params = nativeRequest.params;
|
|
443
446
|
}
|
|
447
|
+
get connection() {
|
|
448
|
+
return this._connection ??= {
|
|
449
|
+
encrypted: this._parsedUrl.protocol === "https:" || this.nativeRequest.url.startsWith("https://")
|
|
450
|
+
};
|
|
451
|
+
}
|
|
444
452
|
get socket() {
|
|
445
|
-
return {
|
|
446
|
-
encrypted: this._parsedUrl.protocol === "https:",
|
|
453
|
+
return this._socket ??= {
|
|
454
|
+
encrypted: this._parsedUrl.protocol === "https:" || this.nativeRequest.url.startsWith("https://"),
|
|
447
455
|
setKeepAlive: () => {},
|
|
448
456
|
setNoDelay: () => {},
|
|
449
457
|
setTimeout: () => {}
|
|
450
458
|
};
|
|
451
459
|
}
|
|
452
460
|
get url() {
|
|
453
|
-
return this._parsedUrl.pathname + this._parsedUrl.search;
|
|
461
|
+
return this._url ??= this._parsedUrl.pathname + this._parsedUrl.search;
|
|
454
462
|
}
|
|
455
463
|
original() {
|
|
456
464
|
return this.nativeRequest;
|
|
@@ -531,14 +539,18 @@ class BunRequest {
|
|
|
531
539
|
}
|
|
532
540
|
clone() {
|
|
533
541
|
const cloned = new BunRequest(this.nativeRequest.clone());
|
|
542
|
+
cloned._hostname = this._hostname;
|
|
534
543
|
cloned._pathname = this._pathname;
|
|
544
|
+
cloned._query = this._query;
|
|
535
545
|
cloned._body = this._body;
|
|
536
546
|
cloned._rawBody = this._rawBody;
|
|
537
547
|
cloned._file = this._file;
|
|
538
548
|
cloned._files = this._files;
|
|
539
549
|
cloned._headers = this._headers;
|
|
540
|
-
cloned._query = this._query;
|
|
541
550
|
cloned._settings = this._settings;
|
|
551
|
+
cloned._connection = this._connection;
|
|
552
|
+
cloned._socket = this._socket;
|
|
553
|
+
cloned._url = this._url;
|
|
542
554
|
return cloned;
|
|
543
555
|
}
|
|
544
556
|
on(event, listener) {
|
|
@@ -797,6 +809,9 @@ class BunResponse {
|
|
|
797
809
|
isEnded() {
|
|
798
810
|
return this.ended;
|
|
799
811
|
}
|
|
812
|
+
_implicitHeader() {
|
|
813
|
+
this.writeHead(this.statusCode, {});
|
|
814
|
+
}
|
|
800
815
|
buildStreamableResponse(body) {
|
|
801
816
|
const streamHeaders = body.getHeaders();
|
|
802
817
|
const headers = this.headersMap;
|
|
@@ -1045,6 +1060,7 @@ class BunServerInstance {
|
|
|
1045
1060
|
wsOptions = {};
|
|
1046
1061
|
useWs = false;
|
|
1047
1062
|
useWsCors = false;
|
|
1063
|
+
staticAssetsOptions = null;
|
|
1048
1064
|
httpServer = null;
|
|
1049
1065
|
constructor(bunServeOptions) {
|
|
1050
1066
|
this.bunServeOptions = bunServeOptions;
|
|
@@ -1125,13 +1141,14 @@ class BunServerInstance {
|
|
|
1125
1141
|
search(pathOrHandler, maybeHandler) {
|
|
1126
1142
|
this.createUnsupportedMethod()(pathOrHandler, maybeHandler);
|
|
1127
1143
|
}
|
|
1128
|
-
listen(port, hostnameOrCallback, maybeCallback) {
|
|
1144
|
+
async listen(port, hostnameOrCallback, maybeCallback) {
|
|
1129
1145
|
const hostname = typeof hostnameOrCallback === "string" ? hostnameOrCallback : this.bunServeOptions.hostname ?? "127.0.0.1";
|
|
1130
1146
|
const callback = typeof hostnameOrCallback === "function" ? hostnameOrCallback : maybeCallback;
|
|
1131
1147
|
const middlewareEngine = this.middlewareEngine;
|
|
1132
1148
|
const notFoundHandler = this.notFoundHandler;
|
|
1133
1149
|
const wsHandlers = this.wsHandlers;
|
|
1134
1150
|
const bunServeOptions = this.bunServeOptions;
|
|
1151
|
+
await this.setupStaticAssetsIfNeeded();
|
|
1135
1152
|
this.setupWebSocketIfNeeded(wsHandlers, bunServeOptions);
|
|
1136
1153
|
const fetch = async (request, server) => {
|
|
1137
1154
|
const bunRequest = new BunRequest(request);
|
|
@@ -1222,6 +1239,10 @@ class BunServerInstance {
|
|
|
1222
1239
|
const corsMiddleware = new BunCorsMiddleware({ corsOptions, prefix });
|
|
1223
1240
|
this.middlewareEngine.useGlobal(corsMiddleware.run.bind(corsMiddleware));
|
|
1224
1241
|
}
|
|
1242
|
+
useStaticAssets(path, options) {
|
|
1243
|
+
this.logger.log(`Configuring static assets serving from path: ${path} with options: ${JSON.stringify(options)}`);
|
|
1244
|
+
this.staticAssetsOptions = { path, options };
|
|
1245
|
+
}
|
|
1225
1246
|
static isNumericPort(value) {
|
|
1226
1247
|
return typeof value === "number" || !isNaN(Number(value));
|
|
1227
1248
|
}
|
|
@@ -1261,6 +1282,70 @@ class BunServerInstance {
|
|
|
1261
1282
|
data: await this.wsOptions.clientDataFactory?.(bunRequest) ?? {}
|
|
1262
1283
|
});
|
|
1263
1284
|
}
|
|
1285
|
+
async setupStaticAssetsIfNeeded() {
|
|
1286
|
+
if (!this.staticAssetsOptions)
|
|
1287
|
+
return;
|
|
1288
|
+
const { path, options } = this.staticAssetsOptions;
|
|
1289
|
+
const files = await readdir(path, { withFileTypes: true, recursive: true });
|
|
1290
|
+
const flattenFiles = files.flat(Infinity);
|
|
1291
|
+
if (flattenFiles.length === 0)
|
|
1292
|
+
return;
|
|
1293
|
+
const useStatic = options?.useStatic ?? false;
|
|
1294
|
+
for (const file of flattenFiles) {
|
|
1295
|
+
if (!file.isFile())
|
|
1296
|
+
continue;
|
|
1297
|
+
const relativePath = file.parentPath.replace(path, "");
|
|
1298
|
+
const routePath = relativePath.startsWith("/") ? [relativePath, file.name].join("/") : `/${file.name}`;
|
|
1299
|
+
if (useStatic) {
|
|
1300
|
+
const bunFile = Bun.file(join(file.parentPath, file.name));
|
|
1301
|
+
this.routes[routePath] = {
|
|
1302
|
+
GET: new Response(await bunFile.bytes(), {
|
|
1303
|
+
headers: {
|
|
1304
|
+
"Content-Type": bunFile.type
|
|
1305
|
+
}
|
|
1306
|
+
})
|
|
1307
|
+
};
|
|
1308
|
+
} else {
|
|
1309
|
+
this.delegateRouteHandler("GET", routePath, async (req, res) => {
|
|
1310
|
+
const bunFile = Bun.file(join(file.parentPath, file.name));
|
|
1311
|
+
if (!await bunFile.exists()) {
|
|
1312
|
+
this.notFoundHandler(req, res);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const ifModifiedSince = req.headers.get("if-modified-since");
|
|
1316
|
+
if (ifModifiedSince) {
|
|
1317
|
+
const lastModified = bunFile.lastModified;
|
|
1318
|
+
if (new Date(ifModifiedSince).getTime() >= lastModified) {
|
|
1319
|
+
res.setStatus(304);
|
|
1320
|
+
res.end();
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
const range = req.headers.get("range");
|
|
1325
|
+
if (range) {
|
|
1326
|
+
const fileSize = bunFile.size;
|
|
1327
|
+
const match = /bytes=(\d*)-(\d*)/.exec(range);
|
|
1328
|
+
if (match) {
|
|
1329
|
+
const start = parseInt(match[1], 10) || 0;
|
|
1330
|
+
const end = parseInt(match[2], 10) || fileSize - 1;
|
|
1331
|
+
if (start >= 0 && end < fileSize && start <= end) {
|
|
1332
|
+
res.setStatus(206);
|
|
1333
|
+
res.setHeader("Content-Range", `bytes ${start.toString()}-${end.toString()}/${fileSize.toString()}`);
|
|
1334
|
+
res.end(bunFile.slice(start, end + 1));
|
|
1335
|
+
return;
|
|
1336
|
+
} else {
|
|
1337
|
+
res.setStatus(416);
|
|
1338
|
+
res.setHeader("Content-Range", `bytes */${fileSize.toString()}`);
|
|
1339
|
+
res.end();
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
res.end(bunFile);
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1264
1349
|
setupWebSocketIfNeeded(wsHandlers, bunServeOptions) {
|
|
1265
1350
|
const useWs = !!wsHandlers.onOpen && !!wsHandlers.onMessage && !!wsHandlers.onClose;
|
|
1266
1351
|
if (!useWs)
|
|
@@ -1399,8 +1484,8 @@ class BunAdapter extends AbstractHttpAdapter {
|
|
|
1399
1484
|
super(new BunServerInstance(bunServeOptions));
|
|
1400
1485
|
this.bunServeOptions = bunServeOptions;
|
|
1401
1486
|
}
|
|
1402
|
-
useStaticAssets(
|
|
1403
|
-
|
|
1487
|
+
useStaticAssets(path, options) {
|
|
1488
|
+
this.instance.useStaticAssets(path, options);
|
|
1404
1489
|
}
|
|
1405
1490
|
setViewEngine(engine) {
|
|
1406
1491
|
throw new Error("Not supported.");
|
|
@@ -1478,7 +1563,9 @@ class BunAdapter extends AbstractHttpAdapter {
|
|
|
1478
1563
|
return BunVersionFilterMiddleware.createFilter(handler, version, versioningOptions);
|
|
1479
1564
|
}
|
|
1480
1565
|
listen(port, hostnameOrCallback, maybeCallback) {
|
|
1481
|
-
this.
|
|
1566
|
+
this.instance.listen(port, hostnameOrCallback, maybeCallback).then((server) => {
|
|
1567
|
+
this.setHttpServer(server);
|
|
1568
|
+
});
|
|
1482
1569
|
}
|
|
1483
1570
|
configureTls(options) {
|
|
1484
1571
|
if (options.httpsOptions) {
|
|
@@ -1497,7 +1584,7 @@ class BunAdapter extends AbstractHttpAdapter {
|
|
|
1497
1584
|
}
|
|
1498
1585
|
// lib/bun.file.interceptor.ts
|
|
1499
1586
|
import { Injectable } from "@nestjs/common";
|
|
1500
|
-
import { basename, join } from "path";
|
|
1587
|
+
import { basename, join as join2 } from "path";
|
|
1501
1588
|
import { HttpAdapterHost } from "@nestjs/core";
|
|
1502
1589
|
var {randomUUIDv7: randomUUIDv72 } = globalThis.Bun;
|
|
1503
1590
|
import { tmpdir } from "os";
|
|
@@ -1507,7 +1594,7 @@ class BunFileInterceptor {
|
|
|
1507
1594
|
constructor(adapter) {
|
|
1508
1595
|
this.adapter = adapter;
|
|
1509
1596
|
const httpAdapter = this.adapter.httpAdapter;
|
|
1510
|
-
this.uploadDir ??=
|
|
1597
|
+
this.uploadDir ??= join2(tmpdir(), "uploads", httpAdapter.getHttpServer().id, randomUUIDv72());
|
|
1511
1598
|
}
|
|
1512
1599
|
async intercept(context, next) {
|
|
1513
1600
|
const request = context.switchToHttp().getRequest();
|
|
@@ -1515,7 +1602,7 @@ class BunFileInterceptor {
|
|
|
1515
1602
|
return next.handle();
|
|
1516
1603
|
}
|
|
1517
1604
|
const files = await Promise.all(request.files.map(async (file) => {
|
|
1518
|
-
const destPath =
|
|
1605
|
+
const destPath = join2(this.uploadDir, basename(file.name));
|
|
1519
1606
|
await Bun.write(destPath, file);
|
|
1520
1607
|
return Bun.file(destPath);
|
|
1521
1608
|
}));
|
|
@@ -1685,5 +1772,5 @@ export {
|
|
|
1685
1772
|
BunAdapter
|
|
1686
1773
|
};
|
|
1687
1774
|
|
|
1688
|
-
//# debugId=
|
|
1775
|
+
//# debugId=B2EFFD14F95E510364756E2164756E21
|
|
1689
1776
|
//# sourceMappingURL=index.js.map
|