@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
package/src/core/server.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Server } from "bun";
|
|
2
1
|
import { Context } from "./context";
|
|
3
2
|
import { LoggerManager } from "@dangao/logsmith";
|
|
4
3
|
import type { WebSocketGatewayRegistry } from "../websocket/registry";
|
|
5
4
|
import type { WebSocketConnectionData } from "../websocket/registry";
|
|
5
|
+
import type { IServerHandle, IWebSocket } from "../platform/types";
|
|
6
|
+
import { getRuntime } from "../platform/runtime";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 服务器配置选项
|
|
@@ -44,7 +45,7 @@ export interface ServerOptions {
|
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* 连接空闲超时时间(毫秒)
|
|
47
|
-
*
|
|
48
|
+
* Bun 平台下自动转换为秒单位;Node.js 平台下静默忽略
|
|
48
49
|
*/
|
|
49
50
|
idleTimeout?: number;
|
|
50
51
|
|
|
@@ -60,10 +61,10 @@ export interface ServerOptions {
|
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* 服务器封装类
|
|
63
|
-
*
|
|
64
|
+
* 基于平台适配层构建,支持 Bun 和 Node.js
|
|
64
65
|
*/
|
|
65
66
|
export class BunServer {
|
|
66
|
-
private server?:
|
|
67
|
+
private server?: IServerHandle;
|
|
67
68
|
private readonly options: ServerOptions;
|
|
68
69
|
private activeRequests: number = 0;
|
|
69
70
|
private isShuttingDown: boolean = false;
|
|
@@ -77,7 +78,7 @@ export class BunServer {
|
|
|
77
78
|
/**
|
|
78
79
|
* 启动服务器
|
|
79
80
|
*/
|
|
80
|
-
public start(): void {
|
|
81
|
+
public async start(): Promise<void> {
|
|
81
82
|
if (this.server) {
|
|
82
83
|
throw new Error("Server is already running");
|
|
83
84
|
}
|
|
@@ -97,15 +98,15 @@ export class BunServer {
|
|
|
97
98
|
const postProcessSse = (
|
|
98
99
|
response: Response,
|
|
99
100
|
request: Request,
|
|
100
|
-
|
|
101
|
+
serverHandle: IServerHandle,
|
|
101
102
|
): Response => {
|
|
102
103
|
const ct = response.headers.get('content-type');
|
|
103
104
|
if (!ct?.includes('text/event-stream')) {
|
|
104
105
|
return response;
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
// SSE detected —
|
|
108
|
-
|
|
108
|
+
// SSE detected — disable idle timeout for this connection (Bun-only, no-op on Node)
|
|
109
|
+
serverHandle.timeout?.(request, 0);
|
|
109
110
|
|
|
110
111
|
if (sseHeartbeatEnabled && response.body) {
|
|
111
112
|
return BunServer.wrapSseWithHeartbeat(
|
|
@@ -127,8 +128,8 @@ export class BunServer {
|
|
|
127
128
|
|
|
128
129
|
const fetchHandler = (
|
|
129
130
|
request: Request,
|
|
130
|
-
|
|
131
|
-
): Response | Promise<Response> | undefined => {
|
|
131
|
+
serverHandle: IServerHandle,
|
|
132
|
+
): Response | Promise<Response | undefined> | undefined => {
|
|
132
133
|
if (this.isShuttingDown) {
|
|
133
134
|
return new Response("Server is shutting down", { status: 503 });
|
|
134
135
|
}
|
|
@@ -145,7 +146,7 @@ export class BunServer {
|
|
|
145
146
|
}
|
|
146
147
|
const context = new Context(request);
|
|
147
148
|
const queryParams = new URLSearchParams(url.searchParams);
|
|
148
|
-
const upgraded =
|
|
149
|
+
const upgraded = serverHandle.upgrade?.(request, {
|
|
149
150
|
data: {
|
|
150
151
|
path: url.pathname,
|
|
151
152
|
query: queryParams,
|
|
@@ -165,7 +166,7 @@ export class BunServer {
|
|
|
165
166
|
|
|
166
167
|
if (responsePromise instanceof Promise) {
|
|
167
168
|
const processed = responsePromise.then(
|
|
168
|
-
(response) => postProcessSse(response, request,
|
|
169
|
+
(response) => postProcessSse(response, request, serverHandle),
|
|
169
170
|
);
|
|
170
171
|
processed
|
|
171
172
|
.finally(decrementAndMaybeShutdown)
|
|
@@ -174,40 +175,45 @@ export class BunServer {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
decrementAndMaybeShutdown();
|
|
177
|
-
return postProcessSse(responsePromise, request,
|
|
178
|
+
return postProcessSse(responsePromise, request, serverHandle);
|
|
178
179
|
};
|
|
179
180
|
|
|
180
|
-
const websocketHandlers =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
181
|
+
const websocketHandlers = this.options.websocketRegistry
|
|
182
|
+
? {
|
|
183
|
+
open: async (ws: IWebSocket<WebSocketConnectionData>) => {
|
|
184
|
+
await this.options.websocketRegistry?.handleOpen(ws);
|
|
185
|
+
},
|
|
186
|
+
message: async (ws: IWebSocket<WebSocketConnectionData>, message: string | Buffer) => {
|
|
187
|
+
await this.options.websocketRegistry?.handleMessage(ws, message);
|
|
188
|
+
},
|
|
189
|
+
close: async (ws: IWebSocket<WebSocketConnectionData>, code: number, reason: string) => {
|
|
190
|
+
await this.options.websocketRegistry?.handleClose(ws, code, reason);
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
: undefined;
|
|
191
194
|
|
|
195
|
+
const runtime = getRuntime();
|
|
192
196
|
const socketFile = process.env.CLUSTER_SOCKET_FILE;
|
|
193
197
|
|
|
194
198
|
if (socketFile) {
|
|
195
199
|
// Unix socket mode for cluster proxy workers
|
|
196
|
-
this.server =
|
|
200
|
+
this.server = await runtime.http.serve({
|
|
197
201
|
unix: socketFile,
|
|
198
202
|
fetch: fetchHandler,
|
|
199
203
|
websocket: websocketHandlers,
|
|
200
204
|
});
|
|
201
205
|
logger.info(`Server started at unix://${socketFile}`);
|
|
202
206
|
} else {
|
|
203
|
-
|
|
207
|
+
const idleTimeoutSec =
|
|
208
|
+
typeof this.options.idleTimeout === 'number'
|
|
209
|
+
? Math.max(0, Math.ceil(this.options.idleTimeout / 1000))
|
|
210
|
+
: undefined;
|
|
211
|
+
|
|
212
|
+
this.server = await runtime.http.serve({
|
|
204
213
|
port: this.options.port ?? 3000,
|
|
205
214
|
hostname: this.options.hostname,
|
|
206
215
|
reusePort: this.options.reusePort,
|
|
207
|
-
idleTimeout:
|
|
208
|
-
typeof this.options.idleTimeout === 'number'
|
|
209
|
-
? Math.max(0, Math.ceil(this.options.idleTimeout / 1000))
|
|
210
|
-
: undefined,
|
|
216
|
+
idleTimeout: idleTimeoutSec,
|
|
211
217
|
fetch: fetchHandler,
|
|
212
218
|
websocket: websocketHandlers,
|
|
213
219
|
});
|
|
@@ -218,7 +224,7 @@ export class BunServer {
|
|
|
218
224
|
// In proxy cluster mode (TCP fallback), report port to master
|
|
219
225
|
const portFile = process.env.CLUSTER_PORT_FILE;
|
|
220
226
|
if (portFile) {
|
|
221
|
-
|
|
227
|
+
runtime.fs.write(portFile, String(port));
|
|
222
228
|
}
|
|
223
229
|
}
|
|
224
230
|
}
|
|
@@ -301,16 +307,23 @@ export class BunServer {
|
|
|
301
307
|
}
|
|
302
308
|
|
|
303
309
|
/**
|
|
304
|
-
*
|
|
305
|
-
* @returns Bun Server 实例
|
|
310
|
+
* 获取平台中立的服务器句柄(推荐)
|
|
306
311
|
*/
|
|
307
|
-
public getServer():
|
|
312
|
+
public getServer(): IServerHandle | undefined {
|
|
308
313
|
return this.server;
|
|
309
314
|
}
|
|
310
315
|
|
|
316
|
+
/**
|
|
317
|
+
* 获取底层原生服务器实例(不推荐,类型为 unknown)
|
|
318
|
+
* - Bun 平台:Bun.Server<WebSocketConnectionData>
|
|
319
|
+
* - Node.js 平台:node:http.Server
|
|
320
|
+
*/
|
|
321
|
+
public getNativeServer(): unknown {
|
|
322
|
+
return this.server?.getNative();
|
|
323
|
+
}
|
|
324
|
+
|
|
311
325
|
/**
|
|
312
326
|
* 检查服务器是否运行中
|
|
313
|
-
* @returns 是否运行中
|
|
314
327
|
*/
|
|
315
328
|
public isRunning(): boolean {
|
|
316
329
|
return this.server !== undefined;
|
|
@@ -4,6 +4,7 @@ import { createDashboardHTML } from './ui';
|
|
|
4
4
|
import { ControllerRegistry } from '../controller/controller';
|
|
5
5
|
import { HEALTH_INDICATORS_TOKEN } from '../health/types';
|
|
6
6
|
import type { HealthIndicator } from '../health/types';
|
|
7
|
+
import { getRuntime } from '../platform/runtime';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Dashboard 服务
|
|
@@ -122,7 +123,7 @@ export class DashboardService {
|
|
|
122
123
|
heapTotal: mem.heapTotal,
|
|
123
124
|
},
|
|
124
125
|
platform: process.platform,
|
|
125
|
-
bunVersion: typeof Bun !== 'undefined' ? Bun.version : undefined,
|
|
126
|
+
bunVersion: getRuntime().engine === 'bun' && typeof Bun !== 'undefined' ? Bun.version : undefined,
|
|
126
127
|
};
|
|
127
128
|
return new Response(JSON.stringify(data), {
|
|
128
129
|
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
@@ -166,7 +167,7 @@ export class DashboardService {
|
|
|
166
167
|
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
167
168
|
});
|
|
168
169
|
}
|
|
169
|
-
const html =
|
|
170
|
+
const html = getRuntime().parser.renderMarkdown(body.content);
|
|
170
171
|
return new Response(JSON.stringify({ html }), {
|
|
171
172
|
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
172
173
|
});
|
|
@@ -160,21 +160,28 @@ export class DatabaseConnectionManager {
|
|
|
160
160
|
*/
|
|
161
161
|
private async healthCheckSqlite(connection: unknown): Promise<boolean> {
|
|
162
162
|
try {
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
) {
|
|
169
|
-
|
|
170
|
-
const db = connection as {
|
|
171
|
-
query: (sql: string) => {
|
|
172
|
-
all: () => unknown[];
|
|
173
|
-
};
|
|
174
|
-
};
|
|
163
|
+
if (!connection || typeof connection !== 'object') {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// bun:sqlite Database(有 .query() 但没有 callback-based .all())
|
|
168
|
+
if ('query' in connection && typeof (connection as any).query === 'function' && !('all' in connection && 'run' in connection)) {
|
|
169
|
+
const db = connection as { query: (sql: string) => { all: () => unknown[] } };
|
|
175
170
|
db.query('SELECT 1').all();
|
|
176
171
|
return true;
|
|
177
172
|
}
|
|
173
|
+
|
|
174
|
+
// @vscode/sqlite3 Database(callback-based .all())
|
|
175
|
+
if ('all' in connection && typeof (connection as any).all === 'function') {
|
|
176
|
+
await new Promise<void>((resolve, reject) => {
|
|
177
|
+
(connection as any).all('SELECT 1', [], (err: Error | null) => {
|
|
178
|
+
if (err) reject(err);
|
|
179
|
+
else resolve();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
178
185
|
return false;
|
|
179
186
|
} catch (_error) {
|
|
180
187
|
return false;
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
DatabaseConfig,
|
|
4
4
|
DatabaseType,
|
|
5
5
|
} from './types';
|
|
6
|
+
import { getRuntime } from '../platform/runtime';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 连接池中的连接项
|
|
@@ -175,19 +176,23 @@ export class ConnectionPool {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
/**
|
|
178
|
-
* 创建 SQLite
|
|
179
|
+
* 创建 SQLite 连接(自动感知运行时)
|
|
179
180
|
*/
|
|
180
181
|
private async createSqliteConnection(
|
|
181
182
|
config: { path: string },
|
|
182
183
|
): Promise<unknown> {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
if (getRuntime().engine === 'bun') {
|
|
185
|
+
const { Database } = await import('bun:sqlite');
|
|
186
|
+
return new Database(config.path);
|
|
187
|
+
}
|
|
188
|
+
// Node.js:使用 @vscode/sqlite3
|
|
189
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
190
|
+
const sqlite3 = require('@vscode/sqlite3') as any;
|
|
191
|
+
return new sqlite3.Database(config.path);
|
|
187
192
|
}
|
|
188
193
|
|
|
189
194
|
/**
|
|
190
|
-
* 创建 PostgreSQL
|
|
195
|
+
* 创建 PostgreSQL 连接(自动感知运行时)
|
|
191
196
|
*/
|
|
192
197
|
private async createPostgresConnection(
|
|
193
198
|
config: {
|
|
@@ -199,18 +204,18 @@ export class ConnectionPool {
|
|
|
199
204
|
ssl?: boolean;
|
|
200
205
|
},
|
|
201
206
|
): Promise<unknown> {
|
|
202
|
-
// 使用 Bun.SQL API
|
|
203
|
-
// Bun.SQL 支持 postgres:// URL
|
|
204
207
|
const url = `postgres://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
max: 1,
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
if (getRuntime().engine === 'bun') {
|
|
209
|
+
const { SQL } = await import('bun');
|
|
210
|
+
return new SQL(url, { max: 1, tls: config.ssl ?? false });
|
|
211
|
+
}
|
|
212
|
+
// Node.js:使用 postgres 包
|
|
213
|
+
const postgres = require('postgres') as typeof import('postgres');
|
|
214
|
+
return postgres(url, { max: 1, ssl: config.ssl ? 'require' : false });
|
|
210
215
|
}
|
|
211
216
|
|
|
212
217
|
/**
|
|
213
|
-
* 创建 MySQL
|
|
218
|
+
* 创建 MySQL 连接(自动感知运行时)
|
|
214
219
|
*/
|
|
215
220
|
private async createMysqlConnection(
|
|
216
221
|
config: {
|
|
@@ -221,13 +226,21 @@ export class ConnectionPool {
|
|
|
221
226
|
password: string;
|
|
222
227
|
},
|
|
223
228
|
): Promise<unknown> {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
if (getRuntime().engine === 'bun') {
|
|
230
|
+
const url = `mysql://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
|
|
231
|
+
const { SQL } = await import('bun');
|
|
232
|
+
return new SQL(url, { max: 1 });
|
|
233
|
+
}
|
|
234
|
+
// Node.js:使用 mysql2 包
|
|
235
|
+
const mysql2 = require('mysql2/promise') as typeof import('mysql2/promise');
|
|
236
|
+
const conn = await mysql2.createConnection({
|
|
237
|
+
host: config.host,
|
|
238
|
+
port: config.port,
|
|
239
|
+
database: config.database,
|
|
240
|
+
user: config.user,
|
|
241
|
+
password: config.password,
|
|
230
242
|
});
|
|
243
|
+
return conn;
|
|
231
244
|
}
|
|
232
245
|
|
|
233
246
|
/**
|
|
@@ -185,7 +185,7 @@ export class DatabaseModule {
|
|
|
185
185
|
tenantId: selected.tenantId,
|
|
186
186
|
lazyReserve: async () => {
|
|
187
187
|
if (!reserved) {
|
|
188
|
-
reserved = await sql.reserve();
|
|
188
|
+
reserved = await (sql as any).reserve();
|
|
189
189
|
session.reserved = reserved;
|
|
190
190
|
}
|
|
191
191
|
return reserved;
|
package/src/database/db-proxy.ts
CHANGED
|
@@ -49,10 +49,10 @@ const baseDb = async (
|
|
|
49
49
|
if (tenantId) {
|
|
50
50
|
const tenantSql = sqlManager.get(tenantId);
|
|
51
51
|
if (tenantSql) {
|
|
52
|
-
return await tenantSql(strings, ...values);
|
|
52
|
+
return await (tenantSql as any)(strings, ...values);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
return await sqlManager.getDefault()(strings, ...values);
|
|
55
|
+
return await (sqlManager.getDefault() as any)(strings, ...values);
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
function createDb(tenantId?: string): DbProxy {
|
|
@@ -86,7 +86,7 @@ export class TransactionManager {
|
|
|
86
86
|
fn: () => Promise<T>,
|
|
87
87
|
options: TransactionOptions = {},
|
|
88
88
|
): Promise<T> {
|
|
89
|
-
const reserved = await this.sqlManager.getDefault().reserve();
|
|
89
|
+
const reserved = await (this.sqlManager.getDefault() as any).reserve();
|
|
90
90
|
try {
|
|
91
91
|
const tenantId = getCurrentSession()?.tenantId ?? 'default';
|
|
92
92
|
return await runWithSession(
|
package/src/database/service.ts
CHANGED
|
@@ -113,7 +113,7 @@ export class DatabaseService {
|
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
115
|
* 执行 SQL 查询
|
|
116
|
-
* SQLite
|
|
116
|
+
* SQLite (bun:sqlite) 返回同步结果;@vscode/sqlite3 / PostgreSQL / MySQL 返回异步结果
|
|
117
117
|
*/
|
|
118
118
|
public query<T = unknown>(sql: string, params?: unknown[]): T[] | Promise<T[]> {
|
|
119
119
|
const session = getCurrentSession();
|
|
@@ -139,18 +139,20 @@ export class DatabaseService {
|
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* SQLite 查询实现
|
|
142
|
+
* bun:sqlite 使用同步 .query().all();@vscode/sqlite3 使用异步 callback .all()
|
|
142
143
|
*/
|
|
143
144
|
private querySqlite<T = unknown>(
|
|
144
145
|
connection: unknown,
|
|
145
146
|
sql: string,
|
|
146
147
|
params?: unknown[],
|
|
147
|
-
): T[] {
|
|
148
|
-
//
|
|
148
|
+
): T[] | Promise<T[]> {
|
|
149
|
+
// bun:sqlite Database 对象(有 .query() 方法)
|
|
149
150
|
if (
|
|
150
151
|
connection &&
|
|
151
152
|
typeof connection === 'object' &&
|
|
152
153
|
'query' in connection &&
|
|
153
|
-
typeof connection.query === 'function'
|
|
154
|
+
typeof (connection as any).query === 'function' &&
|
|
155
|
+
!('all' in connection && 'run' in connection)
|
|
154
156
|
) {
|
|
155
157
|
const db = connection as {
|
|
156
158
|
query: (sql: string) => {
|
|
@@ -160,12 +162,26 @@ export class DatabaseService {
|
|
|
160
162
|
};
|
|
161
163
|
|
|
162
164
|
const statement = db.query(sql);
|
|
163
|
-
// Bun SQLite 的 all() 方法接受参数
|
|
164
165
|
const result =
|
|
165
166
|
params && params.length > 0 ? statement.all(...params) : statement.all();
|
|
166
167
|
return result;
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
// @vscode/sqlite3 Database 对象(有 .all() callback 方法)
|
|
171
|
+
if (
|
|
172
|
+
connection &&
|
|
173
|
+
typeof connection === 'object' &&
|
|
174
|
+
'all' in connection &&
|
|
175
|
+
typeof (connection as any).all === 'function'
|
|
176
|
+
) {
|
|
177
|
+
return new Promise<T[]>((resolve, reject) => {
|
|
178
|
+
(connection as any).all(sql, params ?? [], (err: Error | null, rows: T[]) => {
|
|
179
|
+
if (err) reject(err);
|
|
180
|
+
else resolve(rows ?? []);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
169
185
|
throw new Error('Invalid SQLite connection');
|
|
170
186
|
}
|
|
171
187
|
|
|
@@ -1,24 +1,55 @@
|
|
|
1
|
-
import { SQL } from 'bun';
|
|
2
|
-
|
|
3
1
|
import type { BunSQLConfig } from './types';
|
|
2
|
+
import { getRuntime } from '../platform/runtime';
|
|
4
3
|
|
|
4
|
+
/**
|
|
5
|
+
* SQL 连接管理器
|
|
6
|
+
* 在 Bun 平台下使用 Bun.SQL,在 Node.js 平台下使用 postgres/mysql2
|
|
7
|
+
* 内部自动感知运行时,用户无需关心底层实现
|
|
8
|
+
*/
|
|
5
9
|
export class BunSQLManager {
|
|
6
|
-
private readonly instances = new Map<string,
|
|
10
|
+
private readonly instances = new Map<string, unknown>();
|
|
7
11
|
private defaultTenantId = 'default';
|
|
8
12
|
|
|
9
|
-
public getOrCreate(tenantId: string, config: BunSQLConfig):
|
|
13
|
+
public getOrCreate(tenantId: string, config: BunSQLConfig): unknown {
|
|
10
14
|
const existing = this.instances.get(tenantId);
|
|
11
15
|
if (existing) {
|
|
12
16
|
return existing;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
const pool = config.pool ?? {};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
let sql: unknown;
|
|
21
|
+
|
|
22
|
+
if (getRuntime().engine === 'bun') {
|
|
23
|
+
const { SQL } = require('bun') as typeof import('bun');
|
|
24
|
+
sql = new SQL(config.url, {
|
|
25
|
+
max: pool.max ?? 10,
|
|
26
|
+
idleTimeout: pool.idleTimeout ?? 30,
|
|
27
|
+
maxLifetime: pool.maxLifetime ?? 0,
|
|
28
|
+
connectionTimeout: pool.connectionTimeout ?? 30000,
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
// Node.js: detect dialect from URL
|
|
32
|
+
const url = config.url.toLowerCase();
|
|
33
|
+
if (url.startsWith('mysql://') || url.startsWith('mysql2://')) {
|
|
34
|
+
const mysql2 = require('mysql2/promise') as typeof import('mysql2/promise');
|
|
35
|
+
// Create a pool in Node.js
|
|
36
|
+
sql = mysql2.createPool({
|
|
37
|
+
uri: config.url,
|
|
38
|
+
connectionLimit: pool.max ?? 10,
|
|
39
|
+
waitForConnections: true,
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
// postgres (default)
|
|
43
|
+
const postgres = require('postgres') as typeof import('postgres');
|
|
44
|
+
sql = postgres(config.url, {
|
|
45
|
+
max: pool.max ?? 10,
|
|
46
|
+
idle_timeout: pool.idleTimeout ?? 30,
|
|
47
|
+
max_lifetime: pool.maxLifetime ?? 0,
|
|
48
|
+
connect_timeout: (pool.connectionTimeout ?? 30000) / 1000,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
22
53
|
this.instances.set(tenantId, sql);
|
|
23
54
|
return sql;
|
|
24
55
|
}
|
|
@@ -27,7 +58,7 @@ export class BunSQLManager {
|
|
|
27
58
|
return this.instances.has(tenantId);
|
|
28
59
|
}
|
|
29
60
|
|
|
30
|
-
public get(tenantId: string):
|
|
61
|
+
public get(tenantId: string): unknown {
|
|
31
62
|
return this.instances.get(tenantId);
|
|
32
63
|
}
|
|
33
64
|
|
|
@@ -35,7 +66,7 @@ export class BunSQLManager {
|
|
|
35
66
|
this.defaultTenantId = tenantId;
|
|
36
67
|
}
|
|
37
68
|
|
|
38
|
-
public getDefault():
|
|
69
|
+
public getDefault(): unknown {
|
|
39
70
|
const sql = this.instances.get(this.defaultTenantId);
|
|
40
71
|
if (!sql) {
|
|
41
72
|
throw new Error(
|
|
@@ -50,7 +81,11 @@ export class BunSQLManager {
|
|
|
50
81
|
if (!sql) {
|
|
51
82
|
return;
|
|
52
83
|
}
|
|
53
|
-
|
|
84
|
+
if (typeof (sql as any).close === 'function') {
|
|
85
|
+
await (sql as any).close({ timeout });
|
|
86
|
+
} else if (typeof (sql as any).end === 'function') {
|
|
87
|
+
await (sql as any).end();
|
|
88
|
+
}
|
|
54
89
|
this.instances.delete(tenantId);
|
|
55
90
|
}
|
|
56
91
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Database, type SQLQueryBindings } from 'bun:sqlite';
|
|
2
|
-
|
|
3
1
|
import type { SqliteV2Config } from './types';
|
|
2
|
+
import { getRuntime } from '../platform/runtime';
|
|
4
3
|
|
|
5
4
|
export interface DisposableLock {
|
|
6
5
|
[Symbol.dispose](): void;
|
|
@@ -39,30 +38,72 @@ export class Semaphore {
|
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
|
|
41
|
+
/**
|
|
42
|
+
* SQLite 适配器(自动感知运行时)
|
|
43
|
+
* Bun 平台下使用 bun:sqlite,Node.js 平台下使用 @vscode/sqlite3
|
|
44
|
+
*/
|
|
42
45
|
export class SqliteAdapter {
|
|
43
|
-
private readonly db:
|
|
46
|
+
private readonly db: unknown;
|
|
44
47
|
public readonly semaphore: Semaphore;
|
|
48
|
+
private readonly isBun: boolean;
|
|
45
49
|
|
|
46
50
|
public constructor(config: SqliteV2Config) {
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
this.isBun = getRuntime().engine === 'bun';
|
|
52
|
+
|
|
53
|
+
if (this.isBun) {
|
|
54
|
+
const { Database } = require('bun:sqlite') as typeof import('bun:sqlite');
|
|
55
|
+
const db = new Database(config.database);
|
|
56
|
+
if (config.wal !== false) {
|
|
57
|
+
db.exec('PRAGMA journal_mode = WAL;');
|
|
58
|
+
}
|
|
59
|
+
this.db = db;
|
|
60
|
+
} else {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
|
+
const sqlite3 = require('@vscode/sqlite3') as any;
|
|
63
|
+
const db: any = new sqlite3.Database(config.database);
|
|
64
|
+
if (config.wal !== false) {
|
|
65
|
+
// Operations are serialized internally; WAL is queued before any query runs
|
|
66
|
+
db.run('PRAGMA journal_mode = WAL;');
|
|
67
|
+
}
|
|
68
|
+
this.db = db;
|
|
50
69
|
}
|
|
70
|
+
|
|
51
71
|
this.semaphore = new Semaphore(config.maxWriteConcurrency ?? 1);
|
|
52
72
|
}
|
|
53
73
|
|
|
54
|
-
public query<T = unknown>(sql: string, params:
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
public async query<T = unknown>(sql: string, params: unknown[] = []): Promise<T[]> {
|
|
75
|
+
if (this.isBun) {
|
|
76
|
+
const db = this.db as import('bun:sqlite').Database;
|
|
77
|
+
const stmt = db.query(sql);
|
|
78
|
+
return stmt.all(...params as Parameters<typeof stmt.all>) as T[];
|
|
79
|
+
}
|
|
80
|
+
return new Promise<T[]>((resolve, reject) => {
|
|
81
|
+
(this.db as any).all(sql, params, (err: Error | null, rows: T[]) => {
|
|
82
|
+
if (err) reject(err);
|
|
83
|
+
else resolve(rows ?? []);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
57
86
|
}
|
|
58
87
|
|
|
59
|
-
public async execute(sql: string, params:
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
public async execute(sql: string, params: unknown[] = []): Promise<void> {
|
|
89
|
+
if (this.isBun) {
|
|
90
|
+
const db = this.db as import('bun:sqlite').Database;
|
|
91
|
+
const stmt = db.query(sql);
|
|
92
|
+
stmt.run(...params as Parameters<typeof stmt.run>);
|
|
93
|
+
} else {
|
|
94
|
+
return new Promise<void>((resolve, reject) => {
|
|
95
|
+
(this.db as any).run(sql, params, (err: Error | null) => {
|
|
96
|
+
if (err) reject(err);
|
|
97
|
+
else resolve();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
62
101
|
}
|
|
63
102
|
|
|
64
103
|
public close(): void {
|
|
65
|
-
this.db.close
|
|
104
|
+
if (typeof (this.db as any).close === 'function') {
|
|
105
|
+
(this.db as any).close();
|
|
106
|
+
}
|
|
66
107
|
}
|
|
67
108
|
}
|
|
68
109
|
|
package/src/debug/recorder.ts
CHANGED
|
@@ -110,11 +110,11 @@ export class RequestRecorder {
|
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* 从 JSONL 内容导入请求记录
|
|
113
|
-
* 利用 Bun 1.3.7+ 原生 Bun.JSONL.parse() 高性能解析
|
|
114
113
|
* @param content - JSONL 格式文本
|
|
115
114
|
*/
|
|
116
115
|
public importFromJsonl(content: string): void {
|
|
117
|
-
const
|
|
116
|
+
const { getRuntime } = require('../platform/runtime') as typeof import('../platform/runtime');
|
|
117
|
+
const records = getRuntime().parser.parseJSONL(content) as RequestRecord[];
|
|
118
118
|
for (const record of records) {
|
|
119
119
|
if (record.id) {
|
|
120
120
|
const oldRecord = this.buffer[this.writeIndex];
|
|
@@ -136,6 +136,7 @@ export class RequestRecorder {
|
|
|
136
136
|
* @param content - JSONL 格式文本
|
|
137
137
|
*/
|
|
138
138
|
public static parseJsonl(content: string): RequestRecord[] {
|
|
139
|
-
|
|
139
|
+
const { getRuntime } = require('../platform/runtime') as typeof import('../platform/runtime');
|
|
140
|
+
return getRuntime().parser.parseJSONL(content) as RequestRecord[];
|
|
140
141
|
}
|
|
141
142
|
}
|