@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.
Files changed (151) hide show
  1. package/README.md +81 -3
  2. package/dist/auth/jwt.d.ts.map +1 -1
  3. package/dist/config/service.d.ts +0 -1
  4. package/dist/config/service.d.ts.map +1 -1
  5. package/dist/core/application.d.ts +13 -0
  6. package/dist/core/application.d.ts.map +1 -1
  7. package/dist/core/cluster.d.ts.map +1 -1
  8. package/dist/core/server.d.ts +12 -9
  9. package/dist/core/server.d.ts.map +1 -1
  10. package/dist/dashboard/controller.d.ts.map +1 -1
  11. package/dist/database/connection-manager.d.ts.map +1 -1
  12. package/dist/database/connection-pool.d.ts +3 -3
  13. package/dist/database/connection-pool.d.ts.map +1 -1
  14. package/dist/database/service.d.ts +2 -1
  15. package/dist/database/service.d.ts.map +1 -1
  16. package/dist/database/sql-manager.d.ts +8 -4
  17. package/dist/database/sql-manager.d.ts.map +1 -1
  18. package/dist/database/sqlite-adapter.d.ts +7 -3
  19. package/dist/database/sqlite-adapter.d.ts.map +1 -1
  20. package/dist/debug/recorder.d.ts +0 -1
  21. package/dist/debug/recorder.d.ts.map +1 -1
  22. package/dist/files/static-middleware.d.ts.map +1 -1
  23. package/dist/files/storage.d.ts.map +1 -1
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +39487 -3496
  27. package/dist/index.node.mjs +17723 -0
  28. package/dist/middleware/builtin/static-file.d.ts +4 -2
  29. package/dist/middleware/builtin/static-file.d.ts.map +1 -1
  30. package/dist/platform/bun/crypto.d.ts +3 -0
  31. package/dist/platform/bun/crypto.d.ts.map +1 -0
  32. package/dist/platform/bun/fs.d.ts +3 -0
  33. package/dist/platform/bun/fs.d.ts.map +1 -0
  34. package/dist/platform/bun/http.d.ts +15 -0
  35. package/dist/platform/bun/http.d.ts.map +1 -0
  36. package/dist/platform/bun/index.d.ts +3 -0
  37. package/dist/platform/bun/index.d.ts.map +1 -0
  38. package/dist/platform/bun/parser.d.ts +3 -0
  39. package/dist/platform/bun/parser.d.ts.map +1 -0
  40. package/dist/platform/bun/process.d.ts +3 -0
  41. package/dist/platform/bun/process.d.ts.map +1 -0
  42. package/dist/platform/detector.d.ts +9 -0
  43. package/dist/platform/detector.d.ts.map +1 -0
  44. package/dist/platform/index.d.ts +4 -0
  45. package/dist/platform/index.d.ts.map +1 -0
  46. package/dist/platform/node/crypto.d.ts +3 -0
  47. package/dist/platform/node/crypto.d.ts.map +1 -0
  48. package/dist/platform/node/fs.d.ts +3 -0
  49. package/dist/platform/node/fs.d.ts.map +1 -0
  50. package/dist/platform/node/http.d.ts +3 -0
  51. package/dist/platform/node/http.d.ts.map +1 -0
  52. package/dist/platform/node/index.d.ts +3 -0
  53. package/dist/platform/node/index.d.ts.map +1 -0
  54. package/dist/platform/node/parser.d.ts +3 -0
  55. package/dist/platform/node/parser.d.ts.map +1 -0
  56. package/dist/platform/node/process.d.ts +3 -0
  57. package/dist/platform/node/process.d.ts.map +1 -0
  58. package/dist/platform/runtime.d.ts +14 -0
  59. package/dist/platform/runtime.d.ts.map +1 -0
  60. package/dist/platform/types.d.ts +139 -0
  61. package/dist/platform/types.d.ts.map +1 -0
  62. package/dist/prompt/stores/file-store.d.ts.map +1 -1
  63. package/dist/rag/service.d.ts.map +1 -1
  64. package/dist/request/response.d.ts +3 -1
  65. package/dist/request/response.d.ts.map +1 -1
  66. package/dist/security/guards/execution-context.d.ts +2 -2
  67. package/dist/security/guards/execution-context.d.ts.map +1 -1
  68. package/dist/security/guards/types.d.ts +2 -2
  69. package/dist/security/guards/types.d.ts.map +1 -1
  70. package/dist/swagger/generator.d.ts.map +1 -1
  71. package/dist/websocket/registry.d.ts +4 -4
  72. package/dist/websocket/registry.d.ts.map +1 -1
  73. package/docs/deployment.md +31 -7
  74. package/docs/idle-timeout.md +6 -4
  75. package/docs/migration.md +43 -0
  76. package/docs/platform.md +299 -0
  77. package/docs/testing.md +60 -0
  78. package/docs/zh/deployment.md +30 -7
  79. package/docs/zh/idle-timeout.md +6 -4
  80. package/docs/zh/migration.md +42 -0
  81. package/docs/zh/platform.md +299 -0
  82. package/docs/zh/testing.md +60 -0
  83. package/package.json +24 -7
  84. package/src/auth/jwt.ts +4 -3
  85. package/src/config/service.ts +7 -6
  86. package/src/core/application.ts +19 -1
  87. package/src/core/cluster.ts +16 -14
  88. package/src/core/server.ts +48 -35
  89. package/src/dashboard/controller.ts +3 -2
  90. package/src/database/connection-manager.ts +19 -12
  91. package/src/database/connection-pool.ts +33 -20
  92. package/src/database/database-module.ts +1 -1
  93. package/src/database/db-proxy.ts +2 -2
  94. package/src/database/orm/transaction-manager.ts +1 -1
  95. package/src/database/service.ts +21 -5
  96. package/src/database/sql-manager.ts +48 -13
  97. package/src/database/sqlite-adapter.ts +54 -13
  98. package/src/debug/recorder.ts +4 -3
  99. package/src/files/static-middleware.ts +3 -2
  100. package/src/files/storage.ts +2 -1
  101. package/src/index.ts +13 -0
  102. package/src/middleware/builtin/static-file.ts +8 -5
  103. package/src/platform/bun/crypto.ts +30 -0
  104. package/src/platform/bun/fs.ts +52 -0
  105. package/src/platform/bun/http.ts +106 -0
  106. package/src/platform/bun/index.ts +17 -0
  107. package/src/platform/bun/parser.ts +19 -0
  108. package/src/platform/bun/process.ts +37 -0
  109. package/src/platform/detector.ts +36 -0
  110. package/src/platform/index.ts +20 -0
  111. package/src/platform/node/crypto.ts +40 -0
  112. package/src/platform/node/fs.ts +115 -0
  113. package/src/platform/node/http.ts +196 -0
  114. package/src/platform/node/index.ts +17 -0
  115. package/src/platform/node/parser.ts +34 -0
  116. package/src/platform/node/process.ts +51 -0
  117. package/src/platform/runtime.ts +50 -0
  118. package/src/platform/types.ts +150 -0
  119. package/src/prompt/stores/file-store.ts +6 -5
  120. package/src/rag/service.ts +2 -1
  121. package/src/request/response.ts +7 -4
  122. package/src/security/guards/execution-context.ts +4 -4
  123. package/src/security/guards/types.ts +2 -2
  124. package/src/swagger/generator.ts +2 -1
  125. package/src/websocket/registry.ts +6 -7
  126. package/tests/controller/path-combination.test.ts +196 -2
  127. package/tests/files/static-middleware.test.ts +5 -2
  128. package/tests/middleware/static-file.test.ts +5 -2
  129. package/tests/platform/bun/crypto.test.ts +8 -0
  130. package/tests/platform/bun/database.test.ts +8 -0
  131. package/tests/platform/bun/fs.test.ts +8 -0
  132. package/tests/platform/bun/parser.test.ts +8 -0
  133. package/tests/platform/bun/process.test.ts +8 -0
  134. package/tests/platform/bun/websocket.test.ts +8 -0
  135. package/tests/platform/detector.test.ts +57 -0
  136. package/tests/platform/node/build-smoke.test.ts +92 -0
  137. package/tests/platform/node/crypto.test.ts +9 -0
  138. package/tests/platform/node/database.test.ts +9 -0
  139. package/tests/platform/node/fs.test.ts +9 -0
  140. package/tests/platform/node/parser.test.ts +9 -0
  141. package/tests/platform/node/process.test.ts +9 -0
  142. package/tests/platform/node/websocket.test.ts +9 -0
  143. package/tests/platform/shared/crypto.cases.ts +49 -0
  144. package/tests/platform/shared/database.cases.ts +43 -0
  145. package/tests/platform/shared/fs.cases.ts +82 -0
  146. package/tests/platform/shared/parser.cases.ts +55 -0
  147. package/tests/platform/shared/process.cases.ts +26 -0
  148. package/tests/platform/shared/suite.ts +33 -0
  149. package/tests/platform/shared/websocket.cases.ts +61 -0
  150. package/tests/request/response.test.ts +5 -2
  151. package/tests/router/router-extended.test.ts +53 -0
@@ -0,0 +1,196 @@
1
+ import { createServer as createHttpServer } from 'node:http';
2
+ import type { IncomingMessage, ServerResponse } from 'node:http';
3
+ import type { IHttpDriver, IServerHandle, HttpServeOptions, IWebSocket, WebSocketHandlers } from '../types';
4
+
5
+ /**
6
+ * ws.WebSocket 的 IWebSocket 包装
7
+ */
8
+ class NodeWebSocket<T> implements IWebSocket<T> {
9
+ private readonly ws: import('ws').WebSocket;
10
+ private _data: T;
11
+
12
+ public constructor(ws: import('ws').WebSocket, data: T) {
13
+ this.ws = ws;
14
+ this._data = data;
15
+ }
16
+
17
+ public get data(): T {
18
+ return this._data;
19
+ }
20
+
21
+ public get readyState(): number {
22
+ return this.ws.readyState;
23
+ }
24
+
25
+ public send(data: string | Buffer | Uint8Array): void {
26
+ this.ws.send(data);
27
+ }
28
+
29
+ public close(code?: number, reason?: string): void {
30
+ this.ws.close(code, reason);
31
+ }
32
+ }
33
+
34
+ class NodeServerHandle implements IServerHandle {
35
+ private readonly httpServer: import('node:http').Server;
36
+ private _port: number;
37
+ private _hostname?: string;
38
+
39
+ public constructor(
40
+ httpServer: import('node:http').Server,
41
+ port: number,
42
+ hostname?: string,
43
+ ) {
44
+ this.httpServer = httpServer;
45
+ this._port = port;
46
+ this._hostname = hostname;
47
+ }
48
+
49
+ public get port(): number {
50
+ return this._port;
51
+ }
52
+
53
+ public get hostname(): string | undefined {
54
+ return this._hostname;
55
+ }
56
+
57
+ public stop(): void {
58
+ this.httpServer.close();
59
+ }
60
+
61
+ public getNative(): unknown {
62
+ return this.httpServer;
63
+ }
64
+ }
65
+
66
+ async function nodeRequestToWebRequest(req: IncomingMessage): Promise<Request> {
67
+ const host = req.headers.host ?? 'localhost';
68
+ const url = `http://${host}${req.url ?? '/'}`;
69
+ const method = req.method ?? 'GET';
70
+
71
+ const chunks: Buffer[] = [];
72
+ for await (const chunk of req) {
73
+ chunks.push(chunk as Buffer);
74
+ }
75
+ const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined;
76
+
77
+ const headers = new Headers();
78
+ for (const [key, value] of Object.entries(req.headers)) {
79
+ if (value) {
80
+ if (Array.isArray(value)) {
81
+ for (const v of value) headers.append(key, v);
82
+ } else {
83
+ headers.set(key, value);
84
+ }
85
+ }
86
+ }
87
+
88
+ return new Request(url, {
89
+ method,
90
+ headers,
91
+ body: body?.length ? body : undefined,
92
+ });
93
+ }
94
+
95
+ function sendWebResponse(res: ServerResponse, webResponse: Response): void {
96
+ res.statusCode = webResponse.status;
97
+ webResponse.headers.forEach((value, key) => {
98
+ res.setHeader(key, value);
99
+ });
100
+
101
+ if (!webResponse.body) {
102
+ res.end();
103
+ return;
104
+ }
105
+
106
+ const reader = webResponse.body.getReader();
107
+ const pump = () => {
108
+ reader.read().then(({ done, value }) => {
109
+ if (done) {
110
+ res.end();
111
+ return;
112
+ }
113
+ res.write(value, pump);
114
+ }).catch(() => {
115
+ res.destroy();
116
+ });
117
+ };
118
+ pump();
119
+ }
120
+
121
+ export const nodeHttpAdapter: IHttpDriver = {
122
+ async serve<T>(options: HttpServeOptions<T>): Promise<IServerHandle> {
123
+ // Initialized after listen; the fetch callback only fires after listen completes
124
+ let serverHandle: NodeServerHandle = null!;
125
+
126
+ const httpServer = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
127
+ try {
128
+ const webRequest = await nodeRequestToWebRequest(req);
129
+ const response = await options.fetch(webRequest, serverHandle);
130
+ if (response) {
131
+ sendWebResponse(res, response);
132
+ } else {
133
+ res.statusCode = 404;
134
+ res.end('Not Found');
135
+ }
136
+ } catch (_err) {
137
+ res.statusCode = 500;
138
+ res.end('Internal Server Error');
139
+ }
140
+ });
141
+
142
+ // Mount WebSocket server if handlers provided
143
+ if (options.websocket) {
144
+ setupWebSocket(httpServer, options.websocket);
145
+ }
146
+
147
+ const requestedPort = options.port ?? 3000;
148
+ const hostname = options.hostname;
149
+
150
+ // Wait for the server to actually start listening so port 0 gets resolved
151
+ await new Promise<void>((resolve, reject) => {
152
+ httpServer.once('error', reject);
153
+ httpServer.once('listening', resolve);
154
+ if (hostname) {
155
+ httpServer.listen(requestedPort, hostname);
156
+ } else {
157
+ httpServer.listen(requestedPort);
158
+ }
159
+ });
160
+
161
+ const addr = httpServer.address() as import('node:net').AddressInfo;
162
+ serverHandle = new NodeServerHandle(httpServer, addr.port, hostname);
163
+ return serverHandle;
164
+ },
165
+ };
166
+
167
+ function setupWebSocket<T>(
168
+ httpServer: import('node:http').Server,
169
+ handlers: WebSocketHandlers<T>,
170
+ ): void {
171
+ const { WebSocketServer } = require('ws') as typeof import('ws');
172
+ const wss = new WebSocketServer({ noServer: true });
173
+
174
+ httpServer.on('upgrade', (request, socket, head) => {
175
+ wss.handleUpgrade(request, socket, head, (ws) => {
176
+ const nodeWs = new NodeWebSocket<T>(ws, undefined as unknown as T);
177
+
178
+ if (handlers.open) {
179
+ handlers.open(nodeWs);
180
+ }
181
+
182
+ ws.on('message', (data) => {
183
+ if (handlers.message) {
184
+ const msg = data instanceof Buffer ? data : Buffer.from(data as ArrayBuffer);
185
+ handlers.message(nodeWs, msg);
186
+ }
187
+ });
188
+
189
+ ws.on('close', (code, reason) => {
190
+ if (handlers.close) {
191
+ handlers.close(nodeWs, code, reason.toString());
192
+ }
193
+ });
194
+ });
195
+ });
196
+ }
@@ -0,0 +1,17 @@
1
+ import type { IPlatform } from '../types';
2
+ import { nodeFsAdapter } from './fs';
3
+ import { nodeCryptoAdapter } from './crypto';
4
+ import { nodeParserAdapter } from './parser';
5
+ import { nodeProcessAdapter } from './process';
6
+ import { nodeHttpAdapter } from './http';
7
+
8
+ export function createNodePlatform(): IPlatform {
9
+ return {
10
+ engine: 'node',
11
+ fs: nodeFsAdapter,
12
+ crypto: nodeCryptoAdapter,
13
+ parser: nodeParserAdapter,
14
+ process: nodeProcessAdapter,
15
+ http: nodeHttpAdapter,
16
+ };
17
+ }
@@ -0,0 +1,34 @@
1
+ import type { IParserAdapter } from '../types';
2
+
3
+ export const nodeParserAdapter: IParserAdapter = {
4
+ parseJSONC(content: string): unknown {
5
+ const { parse } = require('jsonc-parser') as typeof import('jsonc-parser');
6
+ return parse(content);
7
+ },
8
+
9
+ parseJSON5(content: string): unknown {
10
+ const JSON5 = require('json5') as typeof import('json5');
11
+ return JSON5.parse(content);
12
+ },
13
+
14
+ parseJSONL(content: string): unknown[] {
15
+ return content
16
+ .split('\n')
17
+ .map((line) => line.trim())
18
+ .filter((line) => line.length > 0)
19
+ .map((line) => JSON.parse(line));
20
+ },
21
+
22
+ renderMarkdown(md: string): string {
23
+ const { marked } = require('marked') as typeof import('marked');
24
+ const result = marked(md);
25
+ if (typeof result === 'string') {
26
+ return result;
27
+ }
28
+ // marked can return a Promise in some configurations — resolve synchronously
29
+ // by using marked.parseInline which is sync, or fall back to a sync config
30
+ const { marked: markedSync } = require('marked') as typeof import('marked');
31
+ markedSync.setOptions({ async: false });
32
+ return markedSync(md) as string;
33
+ },
34
+ };
@@ -0,0 +1,51 @@
1
+ import { spawn as nodeSpawn } from 'node:child_process';
2
+ import type { IProcessAdapter, IChildProcess, SpawnOptions } from '../types';
3
+
4
+ class NodeChildProcess implements IChildProcess {
5
+ private readonly child: ReturnType<typeof nodeSpawn>;
6
+ private readonly _exited: Promise<number | null>;
7
+
8
+ public constructor(child: ReturnType<typeof nodeSpawn>) {
9
+ this.child = child;
10
+ this._exited = new Promise<number | null>((resolve) => {
11
+ child.on('exit', (code) => resolve(code));
12
+ });
13
+ }
14
+
15
+ public get pid(): number {
16
+ return this.child.pid ?? -1;
17
+ }
18
+
19
+ public get exited(): Promise<number | null> {
20
+ return this._exited;
21
+ }
22
+
23
+ public kill(signal?: string | number): void {
24
+ if (typeof signal === 'string') {
25
+ this.child.kill(signal as NodeJS.Signals);
26
+ } else if (typeof signal === 'number') {
27
+ this.child.kill(signal);
28
+ } else {
29
+ this.child.kill('SIGTERM');
30
+ }
31
+ }
32
+ }
33
+
34
+ export const nodeProcessAdapter: IProcessAdapter = {
35
+ spawn(options: SpawnOptions): IChildProcess {
36
+ const [cmd, ...args] = options.cmd;
37
+ const child = nodeSpawn(cmd!, args, {
38
+ env: (options.env ?? process.env) as Record<string, string>,
39
+ stdio: [
40
+ 'ignore',
41
+ options.stdout === 'pipe' ? 'pipe' : options.stdout === 'ignore' ? 'ignore' : 'inherit',
42
+ options.stderr === 'pipe' ? 'pipe' : options.stderr === 'ignore' ? 'ignore' : 'inherit',
43
+ ],
44
+ });
45
+ return new NodeChildProcess(child);
46
+ },
47
+
48
+ async sleep(ms: number): Promise<void> {
49
+ await new Promise<void>((resolve) => setTimeout(resolve, ms));
50
+ },
51
+ };
@@ -0,0 +1,50 @@
1
+ import type { IPlatform, PlatformEngine } from './types';
2
+ import { resolvePlatform } from './detector';
3
+
4
+ let _runtime: IPlatform | null = null;
5
+
6
+ /**
7
+ * 初始化全局运行时平台
8
+ * 应在 Application 构造函数中最先调用
9
+ *
10
+ * @param engine - 显式指定平台(优先级最高),省略则走优先级检测链
11
+ */
12
+ export function initRuntime(engine?: PlatformEngine): void {
13
+ const resolved = resolvePlatform(engine);
14
+
15
+ if (_runtime && _runtime.engine === resolved) {
16
+ return;
17
+ }
18
+
19
+ if (resolved === 'bun') {
20
+ // 动态导入避免 Node.js 环境下引入 Bun 专属 API
21
+ const { createBunPlatform } = require('./bun/index');
22
+ _runtime = createBunPlatform();
23
+ } else {
24
+ const { createNodePlatform } = require('./node/index');
25
+ _runtime = createNodePlatform();
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 获取当前已初始化的平台运行时
31
+ * 必须在 initRuntime() 调用之后使用
32
+ */
33
+ export function getRuntime(): IPlatform {
34
+ if (!_runtime) {
35
+ throw new Error(
36
+ '[Platform] Runtime not initialized. ' +
37
+ 'Make sure initRuntime() is called before using getRuntime(). ' +
38
+ 'This is automatically done by new Application(options).',
39
+ );
40
+ }
41
+ return _runtime;
42
+ }
43
+
44
+ /**
45
+ * 重置运行时(仅用于测试)
46
+ * @internal
47
+ */
48
+ export function _resetRuntime(): void {
49
+ _runtime = null;
50
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * 支持的运行时平台标识
3
+ */
4
+ export type PlatformEngine = 'bun' | 'node';
5
+
6
+ /**
7
+ * 文件引用抽象接口(替代 BunFile 的直接使用)
8
+ */
9
+ export interface IFileRef {
10
+ readonly type: string;
11
+ readonly size: number;
12
+ text(): Promise<string>;
13
+ bytes(): Promise<Uint8Array>;
14
+ arrayBuffer(): Promise<ArrayBuffer>;
15
+ exists(): Promise<boolean>;
16
+ /** 返回可读流,用于 new Response(file.stream(), ...) */
17
+ stream(): ReadableStream<Uint8Array>;
18
+ }
19
+
20
+ /**
21
+ * 文件系统适配器接口
22
+ */
23
+ export interface IFsAdapter {
24
+ /** 获取文件引用 */
25
+ file(path: string): IFileRef;
26
+ /** 写入文件 */
27
+ write(path: string, data: string | Uint8Array | ArrayBuffer): Promise<void>;
28
+ /** 匹配文件列表 */
29
+ glob(pattern: string, cwd?: string): string[];
30
+ }
31
+
32
+ /**
33
+ * 哈希器接口(替代 Bun.CryptoHasher)
34
+ */
35
+ export interface IHasher {
36
+ update(data: string | Uint8Array | ArrayBuffer): IHasher;
37
+ digest(): Uint8Array;
38
+ digest(encoding: 'hex' | 'base64' | 'base64url'): string;
39
+ }
40
+
41
+ /**
42
+ * 加密适配器接口
43
+ */
44
+ export interface ICryptoAdapter {
45
+ createHasher(algorithm: string): IHasher;
46
+ }
47
+
48
+ /**
49
+ * 解析器适配器接口
50
+ */
51
+ export interface IParserAdapter {
52
+ parseJSONC(content: string): unknown;
53
+ parseJSON5(content: string): unknown;
54
+ parseJSONL(content: string): unknown[];
55
+ renderMarkdown(md: string): string;
56
+ }
57
+
58
+ /**
59
+ * 子进程接口
60
+ */
61
+ export interface IChildProcess {
62
+ readonly pid: number;
63
+ readonly exited: Promise<number | null>;
64
+ kill(signal?: string | number): void;
65
+ }
66
+
67
+ /**
68
+ * spawn 选项
69
+ */
70
+ export interface SpawnOptions {
71
+ cmd: string[];
72
+ env?: Record<string, string | undefined>;
73
+ stdout?: 'inherit' | 'pipe' | 'ignore';
74
+ stderr?: 'inherit' | 'pipe' | 'ignore';
75
+ }
76
+
77
+ /**
78
+ * 进程适配器接口
79
+ */
80
+ export interface IProcessAdapter {
81
+ spawn(options: SpawnOptions): IChildProcess;
82
+ sleep(ms: number): Promise<void>;
83
+ }
84
+
85
+ /**
86
+ * WebSocket 连接抽象接口(替代 Bun.ServerWebSocket)
87
+ */
88
+ export interface IWebSocket<T = unknown> {
89
+ readonly data: T;
90
+ readonly readyState: number;
91
+ send(data: string | Buffer | Uint8Array): void;
92
+ close(code?: number, reason?: string): void;
93
+ }
94
+
95
+ /**
96
+ * WebSocket 事件处理器集合
97
+ */
98
+ export interface WebSocketHandlers<T = unknown> {
99
+ open?: (ws: IWebSocket<T>) => void | Promise<void>;
100
+ message?: (ws: IWebSocket<T>, msg: string | Buffer) => void | Promise<void>;
101
+ close?: (ws: IWebSocket<T>, code: number, reason: string) => void | Promise<void>;
102
+ }
103
+
104
+ /**
105
+ * HTTP 服务启动选项
106
+ */
107
+ export interface HttpServeOptions<T = unknown> {
108
+ port?: number;
109
+ hostname?: string;
110
+ reusePort?: boolean;
111
+ idleTimeout?: number;
112
+ /** Unix socket 路径(cluster proxy 模式使用) */
113
+ unix?: string;
114
+ fetch: (request: Request, server: IServerHandle) => Response | Promise<Response | undefined> | undefined;
115
+ websocket?: WebSocketHandlers<T>;
116
+ }
117
+
118
+ /**
119
+ * 服务器句柄接口(替代 Bun.Server)
120
+ */
121
+ export interface IServerHandle {
122
+ readonly port: number;
123
+ readonly hostname?: string;
124
+ stop(): void;
125
+ /** 升级 WebSocket 连接(Bun 原生升级) */
126
+ upgrade?(request: Request, options?: { data?: unknown }): boolean;
127
+ /** 设置连接超时(Bun 专属) */
128
+ timeout?(request: Request, seconds: number): void;
129
+ /** 获取底层原生服务器实例(不推荐,类型为 unknown) */
130
+ getNative(): unknown;
131
+ }
132
+
133
+ /**
134
+ * HTTP 驱动适配器接口
135
+ */
136
+ export interface IHttpDriver {
137
+ serve<T = unknown>(options: HttpServeOptions<T>): Promise<IServerHandle>;
138
+ }
139
+
140
+ /**
141
+ * 平台接口聚合
142
+ */
143
+ export interface IPlatform {
144
+ readonly engine: PlatformEngine;
145
+ readonly fs: IFsAdapter;
146
+ readonly crypto: ICryptoAdapter;
147
+ readonly parser: IParserAdapter;
148
+ readonly process: IProcessAdapter;
149
+ readonly http: IHttpDriver;
150
+ }
@@ -6,6 +6,7 @@ import type {
6
6
  } from '../types';
7
7
  import { extractVariables } from '../types';
8
8
  import { InMemoryPromptStore } from './memory-store';
9
+ import { getRuntime } from '../../platform/runtime';
9
10
 
10
11
  export interface FilePromptStoreConfig {
11
12
  /** Directory containing JSON prompt files (default: './.prompts') */
@@ -73,7 +74,7 @@ export class FilePromptStore implements PromptStore {
73
74
  if (deleted) {
74
75
  try {
75
76
  const path = `${this.promptsDir}/${id}.json`;
76
- await Bun.file(path).exists() && Bun.write(path, ''); // Soft delete (empty file)
77
+ await getRuntime().fs.file(path).exists() && getRuntime().fs.write(path, ''); // Soft delete (empty file)
77
78
  } catch (_error) {
78
79
  // ignore
79
80
  }
@@ -86,12 +87,12 @@ export class FilePromptStore implements PromptStore {
86
87
  this.loaded = true;
87
88
 
88
89
  try {
89
- const glob = new Bun.Glob('*.json');
90
- const files = Array.from(glob.scanSync(this.promptsDir));
90
+ const runtime = getRuntime();
91
+ const files = runtime.fs.glob('*.json', this.promptsDir);
91
92
 
92
93
  for (const file of files) {
93
94
  try {
94
- const content = await Bun.file(`${this.promptsDir}/${file}`).text();
95
+ const content = await runtime.fs.file(`${this.promptsDir}/${file}`).text();
95
96
  if (!content.trim()) continue;
96
97
 
97
98
  const data = JSON.parse(content) as {
@@ -127,7 +128,7 @@ export class FilePromptStore implements PromptStore {
127
128
  null,
128
129
  2,
129
130
  );
130
- await Bun.write(`${this.promptsDir}/${template.id}.json`, content);
131
+ await getRuntime().fs.write(`${this.promptsDir}/${template.id}.json`, content);
131
132
  } catch (_error) {
132
133
  // Ignore write errors (e.g., read-only filesystem)
133
134
  }
@@ -1,5 +1,6 @@
1
1
  import { Injectable } from '../di/decorators';
2
2
  import { Inject } from '../di/decorators';
3
+ import { getRuntime } from '../platform/runtime';
3
4
  import type { EmbeddingService } from '../embedding/service';
4
5
  import { EMBEDDING_SERVICE_TOKEN } from '../embedding/types';
5
6
  import type { VectorStore } from '../vector-store/types';
@@ -120,7 +121,7 @@ export class RagService {
120
121
  return source.content;
121
122
 
122
123
  case 'file': {
123
- const file = Bun.file(source.path);
124
+ const file = getRuntime().fs.file(source.path);
124
125
  return file.text();
125
126
  }
126
127
 
@@ -1,4 +1,7 @@
1
- import type { BodyInit, HeadersInit } from 'bun';
1
+ import { getRuntime } from '../platform/runtime';
2
+
3
+ /** Cross-runtime compatible headers input type */
4
+ type HeadersInit = Headers | string[][] | Record<string, string>;
2
5
 
3
6
  /**
4
7
  * 响应封装类
@@ -131,17 +134,17 @@ export class ResponseBuilder {
131
134
  }
132
135
 
133
136
  if (typeof source === 'string') {
134
- const file = Bun.file(source);
137
+ const file = getRuntime().fs.file(source);
135
138
  if (!headers.has('Content-Type') && file.type) {
136
139
  headers.set('Content-Type', file.type);
137
140
  }
138
- return new Response(file, {
141
+ return new Response(file.stream(), {
139
142
  status: options.status ?? 200,
140
143
  headers,
141
144
  });
142
145
  }
143
146
 
144
- return new Response(source as BodyInit, {
147
+ return new Response(source as any, {
145
148
  status: options.status ?? 200,
146
149
  headers,
147
150
  });
@@ -1,5 +1,5 @@
1
1
  import 'reflect-metadata';
2
- import type { ServerWebSocket } from 'bun';
2
+ import type { IWebSocket } from '../../platform/types';
3
3
  import type { Context } from '../../core/context';
4
4
  import type { ResponseBuilder } from '../../request/response';
5
5
  import type { Constructor } from '../../core/types';
@@ -38,14 +38,14 @@ class HttpArgumentsHostImpl implements HttpArgumentsHost {
38
38
  */
39
39
  class WsArgumentsHostImpl implements WsArgumentsHost {
40
40
  public constructor(
41
- private readonly client: ServerWebSocket<unknown>,
41
+ private readonly client: IWebSocket<unknown>,
42
42
  private readonly data: unknown,
43
43
  ) {}
44
44
 
45
45
  /**
46
46
  * 获取 WebSocket 客户端
47
47
  */
48
- public getClient(): ServerWebSocket<unknown> {
48
+ public getClient(): IWebSocket<unknown> {
49
49
  return this.client;
50
50
  }
51
51
 
@@ -80,7 +80,7 @@ export class ExecutionContextImpl implements ExecutionContext {
80
80
  * @param client - WebSocket 客户端
81
81
  * @param data - 消息数据
82
82
  */
83
- public setWsContext(client: ServerWebSocket<unknown>, data: unknown): void {
83
+ public setWsContext(client: IWebSocket<unknown>, data: unknown): void {
84
84
  this.wsHost = new WsArgumentsHostImpl(client, data);
85
85
  }
86
86
 
@@ -1,7 +1,7 @@
1
1
  import type { Context } from '../../core/context';
2
2
  import type { ResponseBuilder } from '../../request/response';
3
3
  import type { Constructor } from '../../core/types';
4
- import type { ServerWebSocket } from 'bun';
4
+ import type { IWebSocket } from '../../platform/types';
5
5
 
6
6
  /**
7
7
  * 守卫接口
@@ -43,7 +43,7 @@ export interface WsArgumentsHost {
43
43
  * 获取 WebSocket 客户端
44
44
  * @returns WebSocket 连接对象
45
45
  */
46
- getClient(): ServerWebSocket<unknown>;
46
+ getClient(): IWebSocket<unknown>;
47
47
 
48
48
  /**
49
49
  * 获取消息数据
@@ -1,6 +1,7 @@
1
1
  import type { SwaggerDocument, SwaggerOptions, SwaggerPathItem } from './types';
2
2
  import { ControllerRegistry } from '../controller/controller';
3
3
  import { getControllerMetadata, getRouteMetadata } from '../controller/metadata';
4
+ import { getRuntime } from '../platform/runtime';
4
5
  import {
5
6
  getApiTags,
6
7
  getApiOperation,
@@ -311,7 +312,7 @@ export class SwaggerGenerator {
311
312
  */
312
313
  public generateMarkdownHtml(): string {
313
314
  const md = this.generateMarkdown();
314
- return Bun.markdown.html(md, { headings: true });
315
+ return getRuntime().parser.renderMarkdown(md);
315
316
  }
316
317
 
317
318
  /**