@dangao/bun-server 2.3.0 → 3.0.1
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 +81 -3
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/config/service.d.ts +0 -1
- package/dist/config/service.d.ts.map +1 -1
- package/dist/core/application.d.ts +13 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/server.d.ts +12 -9
- package/dist/core/server.d.ts.map +1 -1
- package/dist/dashboard/controller.d.ts.map +1 -1
- package/dist/database/connection-manager.d.ts.map +1 -1
- package/dist/database/connection-pool.d.ts +3 -3
- package/dist/database/connection-pool.d.ts.map +1 -1
- package/dist/database/service.d.ts +2 -1
- package/dist/database/service.d.ts.map +1 -1
- package/dist/database/sql-manager.d.ts +8 -4
- package/dist/database/sql-manager.d.ts.map +1 -1
- package/dist/database/sqlite-adapter.d.ts +7 -3
- package/dist/database/sqlite-adapter.d.ts.map +1 -1
- package/dist/debug/recorder.d.ts +0 -1
- package/dist/debug/recorder.d.ts.map +1 -1
- package/dist/files/static-middleware.d.ts.map +1 -1
- package/dist/files/storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39487 -3496
- package/dist/index.node.mjs +17723 -0
- package/dist/middleware/builtin/static-file.d.ts +4 -2
- package/dist/middleware/builtin/static-file.d.ts.map +1 -1
- package/dist/platform/bun/crypto.d.ts +3 -0
- package/dist/platform/bun/crypto.d.ts.map +1 -0
- package/dist/platform/bun/fs.d.ts +3 -0
- package/dist/platform/bun/fs.d.ts.map +1 -0
- package/dist/platform/bun/http.d.ts +15 -0
- package/dist/platform/bun/http.d.ts.map +1 -0
- package/dist/platform/bun/index.d.ts +3 -0
- package/dist/platform/bun/index.d.ts.map +1 -0
- package/dist/platform/bun/parser.d.ts +3 -0
- package/dist/platform/bun/parser.d.ts.map +1 -0
- package/dist/platform/bun/process.d.ts +3 -0
- package/dist/platform/bun/process.d.ts.map +1 -0
- package/dist/platform/detector.d.ts +9 -0
- package/dist/platform/detector.d.ts.map +1 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/node/crypto.d.ts +3 -0
- package/dist/platform/node/crypto.d.ts.map +1 -0
- package/dist/platform/node/fs.d.ts +3 -0
- package/dist/platform/node/fs.d.ts.map +1 -0
- package/dist/platform/node/http.d.ts +3 -0
- package/dist/platform/node/http.d.ts.map +1 -0
- package/dist/platform/node/index.d.ts +3 -0
- package/dist/platform/node/index.d.ts.map +1 -0
- package/dist/platform/node/parser.d.ts +3 -0
- package/dist/platform/node/parser.d.ts.map +1 -0
- package/dist/platform/node/process.d.ts +3 -0
- package/dist/platform/node/process.d.ts.map +1 -0
- package/dist/platform/runtime.d.ts +14 -0
- package/dist/platform/runtime.d.ts.map +1 -0
- package/dist/platform/types.d.ts +139 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/prompt/stores/file-store.d.ts.map +1 -1
- package/dist/rag/service.d.ts.map +1 -1
- package/dist/request/response.d.ts +3 -1
- package/dist/request/response.d.ts.map +1 -1
- package/dist/security/guards/execution-context.d.ts +2 -2
- package/dist/security/guards/execution-context.d.ts.map +1 -1
- package/dist/security/guards/types.d.ts +2 -2
- package/dist/security/guards/types.d.ts.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/websocket/registry.d.ts +4 -4
- package/dist/websocket/registry.d.ts.map +1 -1
- package/docs/deployment.md +31 -7
- package/docs/idle-timeout.md +6 -4
- package/docs/migration.md +43 -0
- package/docs/platform.md +299 -0
- package/docs/testing.md +60 -0
- package/docs/zh/deployment.md +30 -7
- package/docs/zh/idle-timeout.md +6 -4
- package/docs/zh/migration.md +42 -0
- package/docs/zh/platform.md +299 -0
- package/docs/zh/testing.md +60 -0
- package/package.json +24 -7
- package/src/auth/jwt.ts +4 -3
- package/src/config/service.ts +7 -6
- package/src/core/application.ts +19 -1
- package/src/core/cluster.ts +16 -14
- package/src/core/server.ts +48 -35
- package/src/dashboard/controller.ts +3 -2
- package/src/database/connection-manager.ts +19 -12
- package/src/database/connection-pool.ts +33 -20
- package/src/database/database-module.ts +1 -1
- package/src/database/db-proxy.ts +2 -2
- package/src/database/orm/transaction-manager.ts +1 -1
- package/src/database/service.ts +21 -5
- package/src/database/sql-manager.ts +48 -13
- package/src/database/sqlite-adapter.ts +54 -13
- package/src/debug/recorder.ts +4 -3
- package/src/files/static-middleware.ts +3 -2
- package/src/files/storage.ts +2 -1
- package/src/index.ts +13 -0
- package/src/middleware/builtin/static-file.ts +8 -5
- package/src/platform/bun/crypto.ts +30 -0
- package/src/platform/bun/fs.ts +52 -0
- package/src/platform/bun/http.ts +106 -0
- package/src/platform/bun/index.ts +17 -0
- package/src/platform/bun/parser.ts +19 -0
- package/src/platform/bun/process.ts +37 -0
- package/src/platform/detector.ts +36 -0
- package/src/platform/index.ts +20 -0
- package/src/platform/node/crypto.ts +40 -0
- package/src/platform/node/fs.ts +115 -0
- package/src/platform/node/http.ts +196 -0
- package/src/platform/node/index.ts +17 -0
- package/src/platform/node/parser.ts +34 -0
- package/src/platform/node/process.ts +51 -0
- package/src/platform/runtime.ts +50 -0
- package/src/platform/types.ts +150 -0
- package/src/prompt/stores/file-store.ts +6 -5
- package/src/rag/service.ts +2 -1
- package/src/request/response.ts +7 -4
- package/src/security/guards/execution-context.ts +4 -4
- package/src/security/guards/types.ts +2 -2
- package/src/swagger/generator.ts +2 -1
- package/src/websocket/registry.ts +6 -7
- package/tests/controller/path-combination.test.ts +196 -2
- package/tests/files/static-middleware.test.ts +5 -2
- package/tests/middleware/static-file.test.ts +5 -2
- package/tests/platform/bun/crypto.test.ts +8 -0
- package/tests/platform/bun/database.test.ts +8 -0
- package/tests/platform/bun/fs.test.ts +8 -0
- package/tests/platform/bun/parser.test.ts +8 -0
- package/tests/platform/bun/process.test.ts +8 -0
- package/tests/platform/bun/websocket.test.ts +8 -0
- package/tests/platform/detector.test.ts +57 -0
- package/tests/platform/node/build-smoke.test.ts +92 -0
- package/tests/platform/node/crypto.test.ts +9 -0
- package/tests/platform/node/database.test.ts +9 -0
- package/tests/platform/node/fs.test.ts +9 -0
- package/tests/platform/node/parser.test.ts +9 -0
- package/tests/platform/node/process.test.ts +9 -0
- package/tests/platform/node/websocket.test.ts +9 -0
- package/tests/platform/shared/crypto.cases.ts +49 -0
- package/tests/platform/shared/database.cases.ts +43 -0
- package/tests/platform/shared/fs.cases.ts +82 -0
- package/tests/platform/shared/parser.cases.ts +55 -0
- package/tests/platform/shared/process.cases.ts +26 -0
- package/tests/platform/shared/suite.ts +33 -0
- package/tests/platform/shared/websocket.cases.ts +61 -0
- package/tests/request/response.test.ts +5 -2
- package/tests/router/router-extended.test.ts +53 -0
|
@@ -2,6 +2,7 @@ import { join, normalize } from 'node:path';
|
|
|
2
2
|
import { stat } from 'node:fs/promises';
|
|
3
3
|
|
|
4
4
|
import type { Middleware } from '../middleware';
|
|
5
|
+
import { getRuntime } from '../platform/runtime';
|
|
5
6
|
|
|
6
7
|
export interface StaticFileOptions {
|
|
7
8
|
root: string;
|
|
@@ -35,8 +36,8 @@ export function createStaticFileMiddleware(options: StaticFileOptions): Middlewa
|
|
|
35
36
|
return await next();
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const file =
|
|
39
|
-
return new Response(file, {
|
|
39
|
+
const file = getRuntime().fs.file(filePath);
|
|
40
|
+
return new Response(file.stream(), {
|
|
40
41
|
headers: {
|
|
41
42
|
'Content-Type': file.type || 'application/octet-stream',
|
|
42
43
|
},
|
package/src/files/storage.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { constants } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import type { UploadedFileInfo } from './types';
|
|
6
|
+
import { getRuntime } from '../platform/runtime';
|
|
6
7
|
|
|
7
8
|
export interface SaveFileOptions {
|
|
8
9
|
dest: string;
|
|
@@ -51,7 +52,7 @@ export class FileStorage {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
await
|
|
55
|
+
await getRuntime().fs.write(targetPath, await file.arrayBuffer());
|
|
55
56
|
|
|
56
57
|
return {
|
|
57
58
|
fieldName,
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export { Application, type ApplicationOptions } from './core/application';
|
|
2
|
+
export type {
|
|
3
|
+
PlatformEngine,
|
|
4
|
+
IServerHandle,
|
|
5
|
+
IWebSocket,
|
|
6
|
+
WebSocketHandlers,
|
|
7
|
+
IFsAdapter,
|
|
8
|
+
ICryptoAdapter,
|
|
9
|
+
IParserAdapter,
|
|
10
|
+
IProcessAdapter,
|
|
11
|
+
IHttpDriver,
|
|
12
|
+
IPlatform,
|
|
13
|
+
} from './platform';
|
|
14
|
+
export { getRuntime, initRuntime, resolvePlatform } from './platform';
|
|
2
15
|
export { applyDecorators } from './core/apply-decorators';
|
|
3
16
|
export { BunServer, type ServerOptions } from './core/server';
|
|
4
17
|
export { ClusterManager, type ClusterOptions, type ClusterMode } from './core/cluster';
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { normalize, resolve, sep } from 'path';
|
|
2
2
|
|
|
3
3
|
import type { Middleware } from '../middleware';
|
|
4
|
-
import
|
|
4
|
+
import { getRuntime } from '../../platform/runtime';
|
|
5
|
+
|
|
6
|
+
/** Cross-runtime compatible headers input type */
|
|
7
|
+
type HeadersInput = Headers | string[][] | Record<string, string>;
|
|
5
8
|
|
|
6
9
|
export interface StaticFileOptions {
|
|
7
10
|
root: string;
|
|
8
11
|
prefix?: string;
|
|
9
12
|
indexFile?: string;
|
|
10
13
|
enableCache?: boolean;
|
|
11
|
-
headers?:
|
|
14
|
+
headers?: HeadersInput;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
function isSubPath(root: string, target: string): boolean {
|
|
@@ -65,12 +68,12 @@ export function createStaticFileMiddleware(options: StaticFileOptions): Middlewa
|
|
|
65
68
|
return context.createErrorResponse({ error: 'Forbidden' });
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
const file =
|
|
71
|
+
const file = getRuntime().fs.file(targetPath);
|
|
69
72
|
if (!(await file.exists())) {
|
|
70
73
|
return await next();
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
const headers = new Headers(options.headers);
|
|
76
|
+
const headers = new Headers(options.headers as HeadersInput);
|
|
74
77
|
if (enableCache) {
|
|
75
78
|
headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
|
76
79
|
}
|
|
@@ -78,7 +81,7 @@ export function createStaticFileMiddleware(options: StaticFileOptions): Middlewa
|
|
|
78
81
|
headers.set('Content-Type', file.type);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
return new Response(file, {
|
|
84
|
+
return new Response(file.stream(), {
|
|
82
85
|
status: 200,
|
|
83
86
|
headers,
|
|
84
87
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ICryptoAdapter, IHasher } from '../types';
|
|
2
|
+
|
|
3
|
+
class BunHasher implements IHasher {
|
|
4
|
+
private readonly hasher: Bun.CryptoHasher;
|
|
5
|
+
|
|
6
|
+
public constructor(algorithm: string) {
|
|
7
|
+
this.hasher = new Bun.CryptoHasher(algorithm as any);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public update(data: string | Uint8Array | ArrayBuffer): IHasher {
|
|
11
|
+
this.hasher.update(data as Parameters<Bun.CryptoHasher['update']>[0]);
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public digest(): Uint8Array;
|
|
16
|
+
public digest(encoding: 'hex' | 'base64' | 'base64url'): string;
|
|
17
|
+
public digest(encoding?: 'hex' | 'base64' | 'base64url'): Uint8Array | string {
|
|
18
|
+
if (encoding) {
|
|
19
|
+
return this.hasher.digest(encoding) as string;
|
|
20
|
+
}
|
|
21
|
+
// Bun.CryptoHasher.digest() returns Buffer which extends Uint8Array
|
|
22
|
+
return this.hasher.digest() as unknown as Uint8Array;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const bunCryptoAdapter: ICryptoAdapter = {
|
|
27
|
+
createHasher(algorithm: string): IHasher {
|
|
28
|
+
return new BunHasher(algorithm);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { IFsAdapter, IFileRef } from '../types';
|
|
2
|
+
|
|
3
|
+
class BunFileRef implements IFileRef {
|
|
4
|
+
private readonly bunFile: ReturnType<typeof Bun.file>;
|
|
5
|
+
|
|
6
|
+
public constructor(bunFile: ReturnType<typeof Bun.file>) {
|
|
7
|
+
this.bunFile = bunFile;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public get type(): string {
|
|
11
|
+
return this.bunFile.type;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public get size(): number {
|
|
15
|
+
return this.bunFile.size;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public text(): Promise<string> {
|
|
19
|
+
return this.bunFile.text();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public bytes(): Promise<Uint8Array> {
|
|
23
|
+
return this.bunFile.bytes();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public arrayBuffer(): Promise<ArrayBuffer> {
|
|
27
|
+
return this.bunFile.arrayBuffer();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public exists(): Promise<boolean> {
|
|
31
|
+
return this.bunFile.exists();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public stream(): ReadableStream<Uint8Array> {
|
|
35
|
+
return this.bunFile.stream() as ReadableStream<Uint8Array>;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const bunFsAdapter: IFsAdapter = {
|
|
40
|
+
file(path: string): IFileRef {
|
|
41
|
+
return new BunFileRef(Bun.file(path));
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async write(path: string, data: string | Uint8Array | ArrayBuffer): Promise<void> {
|
|
45
|
+
await Bun.write(path, data as any);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
glob(pattern: string, cwd?: string): string[] {
|
|
49
|
+
const g = new Bun.Glob(pattern);
|
|
50
|
+
return Array.from(g.scanSync(cwd ?? '.'));
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { IHttpDriver, IServerHandle, HttpServeOptions, IWebSocket, WebSocketHandlers } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bun ServerWebSocket 的 IWebSocket 包装
|
|
5
|
+
*/
|
|
6
|
+
export class BunWebSocket<T> implements IWebSocket<T> {
|
|
7
|
+
public constructor(private readonly ws: import('bun').ServerWebSocket<T>) {}
|
|
8
|
+
|
|
9
|
+
public get data(): T {
|
|
10
|
+
return this.ws.data;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public get readyState(): number {
|
|
14
|
+
return this.ws.readyState;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public send(data: string | Buffer | Uint8Array): void {
|
|
18
|
+
this.ws.send(data as string);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public close(code?: number, reason?: string): void {
|
|
22
|
+
this.ws.close(code, reason);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public getNative(): import('bun').ServerWebSocket<T> {
|
|
26
|
+
return this.ws;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class BunServerHandle implements IServerHandle {
|
|
31
|
+
public constructor(private readonly server: import('bun').Server<unknown>) {}
|
|
32
|
+
|
|
33
|
+
public get port(): number {
|
|
34
|
+
return this.server.port ?? 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public get hostname(): string | undefined {
|
|
38
|
+
return this.server.hostname ?? undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public stop(): void {
|
|
42
|
+
this.server.stop();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public upgrade(request: Request, options?: { data?: unknown }): boolean {
|
|
46
|
+
return this.server.upgrade(request, options as any);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public timeout(request: Request, seconds: number): void {
|
|
50
|
+
this.server.timeout(request, seconds);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public getNative(): unknown {
|
|
54
|
+
return this.server;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildWebSocketHandlers<T>(
|
|
59
|
+
handlers?: WebSocketHandlers<T>,
|
|
60
|
+
): import('bun').WebSocketHandler<T> | undefined {
|
|
61
|
+
if (!handlers) return undefined;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
open: handlers.open
|
|
65
|
+
? (ws) => handlers.open!(new BunWebSocket(ws) as IWebSocket<T>)
|
|
66
|
+
: undefined,
|
|
67
|
+
message: handlers.message
|
|
68
|
+
? (ws, msg) => handlers.message!(new BunWebSocket(ws) as IWebSocket<T>, msg as string | Buffer)
|
|
69
|
+
: undefined,
|
|
70
|
+
close: handlers.close
|
|
71
|
+
? (ws, code, reason) => handlers.close!(new BunWebSocket(ws) as IWebSocket<T>, code, reason)
|
|
72
|
+
: undefined,
|
|
73
|
+
} as import('bun').WebSocketHandler<T>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const bunHttpAdapter: IHttpDriver = {
|
|
77
|
+
async serve<T>(options: HttpServeOptions<T>): Promise<IServerHandle> {
|
|
78
|
+
const wsHandlers = buildWebSocketHandlers(options.websocket);
|
|
79
|
+
|
|
80
|
+
const fetchHandler = (request: Request, bunServer: import('bun').Server<unknown>): Response | Promise<Response | undefined> | undefined => {
|
|
81
|
+
const handle = new BunServerHandle(bunServer);
|
|
82
|
+
return options.fetch(request, handle);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let server: import('bun').Server<unknown>;
|
|
86
|
+
|
|
87
|
+
if (options.unix) {
|
|
88
|
+
server = Bun.serve({
|
|
89
|
+
unix: options.unix,
|
|
90
|
+
fetch: fetchHandler,
|
|
91
|
+
websocket: wsHandlers,
|
|
92
|
+
} as any);
|
|
93
|
+
} else {
|
|
94
|
+
server = Bun.serve({
|
|
95
|
+
port: options.port ?? 3000,
|
|
96
|
+
hostname: options.hostname,
|
|
97
|
+
reusePort: options.reusePort,
|
|
98
|
+
idleTimeout: options.idleTimeout,
|
|
99
|
+
fetch: fetchHandler,
|
|
100
|
+
websocket: wsHandlers,
|
|
101
|
+
} as any);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new BunServerHandle(server);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IPlatform } from '../types';
|
|
2
|
+
import { bunFsAdapter } from './fs';
|
|
3
|
+
import { bunCryptoAdapter } from './crypto';
|
|
4
|
+
import { bunParserAdapter } from './parser';
|
|
5
|
+
import { bunProcessAdapter } from './process';
|
|
6
|
+
import { bunHttpAdapter } from './http';
|
|
7
|
+
|
|
8
|
+
export function createBunPlatform(): IPlatform {
|
|
9
|
+
return {
|
|
10
|
+
engine: 'bun',
|
|
11
|
+
fs: bunFsAdapter,
|
|
12
|
+
crypto: bunCryptoAdapter,
|
|
13
|
+
parser: bunParserAdapter,
|
|
14
|
+
process: bunProcessAdapter,
|
|
15
|
+
http: bunHttpAdapter,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IParserAdapter } from '../types';
|
|
2
|
+
|
|
3
|
+
export const bunParserAdapter: IParserAdapter = {
|
|
4
|
+
parseJSONC(content: string): unknown {
|
|
5
|
+
return Bun.JSONC.parse(content);
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
parseJSON5(content: string): unknown {
|
|
9
|
+
return Bun.JSON5.parse(content);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
parseJSONL(content: string): unknown[] {
|
|
13
|
+
return Bun.JSONL.parse(content) as unknown[];
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
renderMarkdown(md: string): string {
|
|
17
|
+
return Bun.markdown.html(md, { headings: true });
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IProcessAdapter, IChildProcess, SpawnOptions } from '../types';
|
|
2
|
+
|
|
3
|
+
class BunChildProcess implements IChildProcess {
|
|
4
|
+
private readonly child: ReturnType<typeof Bun.spawn>;
|
|
5
|
+
|
|
6
|
+
public constructor(child: ReturnType<typeof Bun.spawn>) {
|
|
7
|
+
this.child = child;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public get pid(): number {
|
|
11
|
+
return this.child.pid;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public get exited(): Promise<number | null> {
|
|
15
|
+
return this.child.exited;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public kill(signal?: string | number): void {
|
|
19
|
+
this.child.kill(signal as Parameters<ReturnType<typeof Bun.spawn>['kill']>[0]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const bunProcessAdapter: IProcessAdapter = {
|
|
24
|
+
spawn(options: SpawnOptions): IChildProcess {
|
|
25
|
+
const child = Bun.spawn({
|
|
26
|
+
cmd: options.cmd,
|
|
27
|
+
env: options.env as Record<string, string>,
|
|
28
|
+
stdout: options.stdout ?? 'inherit',
|
|
29
|
+
stderr: options.stderr ?? 'inherit',
|
|
30
|
+
});
|
|
31
|
+
return new BunChildProcess(child);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async sleep(ms: number): Promise<void> {
|
|
35
|
+
await Bun.sleep(ms);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PlatformEngine } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 解析运行时平台引擎
|
|
5
|
+
* 优先级:bootstrap config > CLI arg (--platform=) > env var (BUN_SERVER_PLATFORM) > auto-detect
|
|
6
|
+
*
|
|
7
|
+
* @param bootstrapConfig - 应用启动时显式指定的平台(最高优先级)
|
|
8
|
+
*/
|
|
9
|
+
export function resolvePlatform(bootstrapConfig?: PlatformEngine): PlatformEngine {
|
|
10
|
+
if (bootstrapConfig) {
|
|
11
|
+
return bootstrapConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// CLI arg: --platform=node or --platform=bun
|
|
15
|
+
for (const arg of process.argv) {
|
|
16
|
+
if (arg.startsWith('--platform=')) {
|
|
17
|
+
const value = arg.slice('--platform='.length).trim();
|
|
18
|
+
if (value === 'node' || value === 'bun') {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Env var: BUN_SERVER_PLATFORM=node or BUN_SERVER_PLATFORM=bun
|
|
25
|
+
const envValue = process.env['BUN_SERVER_PLATFORM'];
|
|
26
|
+
if (envValue === 'node' || envValue === 'bun') {
|
|
27
|
+
return envValue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Auto-detect: check if running inside Bun
|
|
31
|
+
if (typeof Bun !== 'undefined') {
|
|
32
|
+
return 'bun';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return 'node';
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
PlatformEngine,
|
|
3
|
+
IPlatform,
|
|
4
|
+
IFsAdapter,
|
|
5
|
+
IFileRef,
|
|
6
|
+
ICryptoAdapter,
|
|
7
|
+
IHasher,
|
|
8
|
+
IParserAdapter,
|
|
9
|
+
IProcessAdapter,
|
|
10
|
+
IChildProcess,
|
|
11
|
+
SpawnOptions,
|
|
12
|
+
IHttpDriver,
|
|
13
|
+
IServerHandle,
|
|
14
|
+
IWebSocket,
|
|
15
|
+
WebSocketHandlers,
|
|
16
|
+
HttpServeOptions,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export { resolvePlatform } from './detector';
|
|
20
|
+
export { initRuntime, getRuntime, _resetRuntime } from './runtime';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createHmac, createHash } from 'node:crypto';
|
|
2
|
+
import type { ICryptoAdapter, IHasher } from '../types';
|
|
3
|
+
|
|
4
|
+
class NodeHasher implements IHasher {
|
|
5
|
+
private data: Buffer = Buffer.alloc(0);
|
|
6
|
+
private readonly algorithm: string;
|
|
7
|
+
|
|
8
|
+
public constructor(algorithm: string) {
|
|
9
|
+
this.algorithm = algorithm;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public update(data: string | Uint8Array | ArrayBuffer): IHasher {
|
|
13
|
+
if (data instanceof ArrayBuffer) {
|
|
14
|
+
this.data = Buffer.concat([this.data, Buffer.from(data)]);
|
|
15
|
+
} else if (typeof data === 'string') {
|
|
16
|
+
this.data = Buffer.concat([this.data, Buffer.from(data, 'utf-8')]);
|
|
17
|
+
} else {
|
|
18
|
+
this.data = Buffer.concat([this.data, Buffer.from(data)]);
|
|
19
|
+
}
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public digest(): Uint8Array;
|
|
24
|
+
public digest(encoding: 'hex' | 'base64' | 'base64url'): string;
|
|
25
|
+
public digest(encoding?: 'hex' | 'base64' | 'base64url'): Uint8Array | string {
|
|
26
|
+
const hash = createHash(this.algorithm);
|
|
27
|
+
hash.update(this.data);
|
|
28
|
+
if (encoding === 'hex') return hash.digest('hex');
|
|
29
|
+
if (encoding === 'base64') return hash.digest('base64');
|
|
30
|
+
if (encoding === 'base64url') return hash.digest('base64url');
|
|
31
|
+
// node:crypto hash.digest() returns Buffer which extends Uint8Array
|
|
32
|
+
return hash.digest();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const nodeCryptoAdapter: ICryptoAdapter = {
|
|
37
|
+
createHasher(algorithm: string): IHasher {
|
|
38
|
+
return new NodeHasher(algorithm);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFile, writeFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { glob } from 'node:fs/promises';
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
5
|
+
import { lookup as mimeLookup } from 'mime-types';
|
|
6
|
+
import { extname } from 'node:path';
|
|
7
|
+
import type { IFsAdapter, IFileRef } from '../types';
|
|
8
|
+
|
|
9
|
+
class NodeFileRef implements IFileRef {
|
|
10
|
+
private readonly path: string;
|
|
11
|
+
private _type: string | null = null;
|
|
12
|
+
private _size: number | null = null;
|
|
13
|
+
|
|
14
|
+
public constructor(path: string) {
|
|
15
|
+
this.path = path;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public get type(): string {
|
|
19
|
+
if (this._type === null) {
|
|
20
|
+
const ext = extname(this.path).toLowerCase();
|
|
21
|
+
this._type = (mimeLookup(ext) || 'application/octet-stream') as string;
|
|
22
|
+
}
|
|
23
|
+
return this._type;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public get size(): number {
|
|
27
|
+
return this._size ?? 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async text(): Promise<string> {
|
|
31
|
+
const buf = await readFile(this.path);
|
|
32
|
+
const stats = await stat(this.path).catch(() => null);
|
|
33
|
+
if (stats) this._size = stats.size;
|
|
34
|
+
return buf.toString('utf-8');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async bytes(): Promise<Uint8Array> {
|
|
38
|
+
const buf = await readFile(this.path);
|
|
39
|
+
const stats = await stat(this.path).catch(() => null);
|
|
40
|
+
if (stats) this._size = stats.size;
|
|
41
|
+
return new Uint8Array(buf);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public async arrayBuffer(): Promise<ArrayBuffer> {
|
|
45
|
+
const bytes = await this.bytes();
|
|
46
|
+
return bytes.buffer as ArrayBuffer;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async exists(): Promise<boolean> {
|
|
50
|
+
return stat(this.path)
|
|
51
|
+
.then((s) => { this._size = s.size; return true; })
|
|
52
|
+
.catch(() => false);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public stream(): ReadableStream<Uint8Array> {
|
|
56
|
+
const nodeStream = createReadStream(this.path);
|
|
57
|
+
// Convert Node.js Readable to Web ReadableStream
|
|
58
|
+
return Readable.toWeb(nodeStream) as ReadableStream<Uint8Array>;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const nodeFsAdapter: IFsAdapter = {
|
|
63
|
+
file(path: string): IFileRef {
|
|
64
|
+
return new NodeFileRef(path);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async write(path: string, data: string | Uint8Array | ArrayBuffer): Promise<void> {
|
|
68
|
+
if (data instanceof ArrayBuffer) {
|
|
69
|
+
await writeFile(path, Buffer.from(data));
|
|
70
|
+
} else {
|
|
71
|
+
await writeFile(path, data as string | Uint8Array);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
glob(pattern: string, cwd?: string): string[] {
|
|
76
|
+
// Use synchronous glob via fast-glob-compatible approach
|
|
77
|
+
// node:fs/promises glob is async; we use sync approach via readdirSync + pattern matching
|
|
78
|
+
const results: string[] = [];
|
|
79
|
+
const base = cwd ?? '.';
|
|
80
|
+
try {
|
|
81
|
+
const { globSync } = require('node:fs');
|
|
82
|
+
if (globSync) {
|
|
83
|
+
// Node 22.0+ has globSync
|
|
84
|
+
const matches = globSync(pattern, { cwd: base });
|
|
85
|
+
return Array.from(matches as string[]);
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// fallback
|
|
89
|
+
}
|
|
90
|
+
// Manual fallback: read dir and match with minimatch-style
|
|
91
|
+
try {
|
|
92
|
+
const entries = require('node:fs').readdirSync(base, { withFileTypes: true, recursive: true }) as import('node:fs').Dirent[];
|
|
93
|
+
const regex = patternToRegex(pattern);
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
if (entry.isFile()) {
|
|
96
|
+
const name = (entry as any).name as string;
|
|
97
|
+
if (regex.test(name)) {
|
|
98
|
+
results.push(name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// ignore
|
|
104
|
+
}
|
|
105
|
+
return results;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
function patternToRegex(pattern: string): RegExp {
|
|
110
|
+
const escaped = pattern
|
|
111
|
+
.replace(/\./g, '\\.')
|
|
112
|
+
.replace(/\*/g, '[^/]*')
|
|
113
|
+
.replace(/\?/g, '[^/]');
|
|
114
|
+
return new RegExp(`^${escaped}$`);
|
|
115
|
+
}
|