@dangao/bun-server 2.3.0 → 3.0.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.
Files changed (147) 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-pool.d.ts +3 -3
  12. package/dist/database/connection-pool.d.ts.map +1 -1
  13. package/dist/database/sql-manager.d.ts +8 -4
  14. package/dist/database/sql-manager.d.ts.map +1 -1
  15. package/dist/database/sqlite-adapter.d.ts +7 -3
  16. package/dist/database/sqlite-adapter.d.ts.map +1 -1
  17. package/dist/debug/recorder.d.ts +0 -1
  18. package/dist/debug/recorder.d.ts.map +1 -1
  19. package/dist/files/static-middleware.d.ts.map +1 -1
  20. package/dist/files/storage.d.ts.map +1 -1
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +40264 -3542
  24. package/dist/index.node.mjs +17689 -0
  25. package/dist/middleware/builtin/static-file.d.ts +4 -2
  26. package/dist/middleware/builtin/static-file.d.ts.map +1 -1
  27. package/dist/platform/bun/crypto.d.ts +3 -0
  28. package/dist/platform/bun/crypto.d.ts.map +1 -0
  29. package/dist/platform/bun/fs.d.ts +3 -0
  30. package/dist/platform/bun/fs.d.ts.map +1 -0
  31. package/dist/platform/bun/http.d.ts +15 -0
  32. package/dist/platform/bun/http.d.ts.map +1 -0
  33. package/dist/platform/bun/index.d.ts +3 -0
  34. package/dist/platform/bun/index.d.ts.map +1 -0
  35. package/dist/platform/bun/parser.d.ts +3 -0
  36. package/dist/platform/bun/parser.d.ts.map +1 -0
  37. package/dist/platform/bun/process.d.ts +3 -0
  38. package/dist/platform/bun/process.d.ts.map +1 -0
  39. package/dist/platform/detector.d.ts +9 -0
  40. package/dist/platform/detector.d.ts.map +1 -0
  41. package/dist/platform/index.d.ts +4 -0
  42. package/dist/platform/index.d.ts.map +1 -0
  43. package/dist/platform/node/crypto.d.ts +3 -0
  44. package/dist/platform/node/crypto.d.ts.map +1 -0
  45. package/dist/platform/node/fs.d.ts +3 -0
  46. package/dist/platform/node/fs.d.ts.map +1 -0
  47. package/dist/platform/node/http.d.ts +3 -0
  48. package/dist/platform/node/http.d.ts.map +1 -0
  49. package/dist/platform/node/index.d.ts +3 -0
  50. package/dist/platform/node/index.d.ts.map +1 -0
  51. package/dist/platform/node/parser.d.ts +3 -0
  52. package/dist/platform/node/parser.d.ts.map +1 -0
  53. package/dist/platform/node/process.d.ts +3 -0
  54. package/dist/platform/node/process.d.ts.map +1 -0
  55. package/dist/platform/runtime.d.ts +14 -0
  56. package/dist/platform/runtime.d.ts.map +1 -0
  57. package/dist/platform/types.d.ts +139 -0
  58. package/dist/platform/types.d.ts.map +1 -0
  59. package/dist/prompt/stores/file-store.d.ts.map +1 -1
  60. package/dist/rag/service.d.ts.map +1 -1
  61. package/dist/request/response.d.ts +3 -1
  62. package/dist/request/response.d.ts.map +1 -1
  63. package/dist/security/guards/execution-context.d.ts +2 -2
  64. package/dist/security/guards/execution-context.d.ts.map +1 -1
  65. package/dist/security/guards/types.d.ts +2 -2
  66. package/dist/security/guards/types.d.ts.map +1 -1
  67. package/dist/swagger/generator.d.ts.map +1 -1
  68. package/dist/websocket/registry.d.ts +4 -4
  69. package/dist/websocket/registry.d.ts.map +1 -1
  70. package/docs/deployment.md +31 -7
  71. package/docs/design/query-interceptor-design.md +381 -0
  72. package/docs/idle-timeout.md +6 -4
  73. package/docs/migration.md +43 -0
  74. package/docs/platform.md +299 -0
  75. package/docs/testing.md +60 -0
  76. package/docs/zh/deployment.md +30 -7
  77. package/docs/zh/idle-timeout.md +6 -4
  78. package/docs/zh/migration.md +42 -0
  79. package/docs/zh/platform.md +299 -0
  80. package/docs/zh/testing.md +60 -0
  81. package/package.json +24 -6
  82. package/src/auth/jwt.ts +4 -3
  83. package/src/config/service.ts +7 -6
  84. package/src/core/application.ts +19 -1
  85. package/src/core/cluster.ts +16 -14
  86. package/src/core/server.ts +48 -35
  87. package/src/dashboard/controller.ts +3 -2
  88. package/src/database/connection-pool.ts +32 -20
  89. package/src/database/database-module.ts +1 -1
  90. package/src/database/db-proxy.ts +2 -2
  91. package/src/database/orm/transaction-manager.ts +1 -1
  92. package/src/database/sql-manager.ts +48 -13
  93. package/src/database/sqlite-adapter.ts +45 -12
  94. package/src/debug/recorder.ts +4 -3
  95. package/src/files/static-middleware.ts +3 -2
  96. package/src/files/storage.ts +2 -1
  97. package/src/index.ts +13 -0
  98. package/src/middleware/builtin/static-file.ts +8 -5
  99. package/src/platform/bun/crypto.ts +30 -0
  100. package/src/platform/bun/fs.ts +52 -0
  101. package/src/platform/bun/http.ts +106 -0
  102. package/src/platform/bun/index.ts +17 -0
  103. package/src/platform/bun/parser.ts +19 -0
  104. package/src/platform/bun/process.ts +37 -0
  105. package/src/platform/detector.ts +36 -0
  106. package/src/platform/index.ts +20 -0
  107. package/src/platform/node/crypto.ts +40 -0
  108. package/src/platform/node/fs.ts +115 -0
  109. package/src/platform/node/http.ts +196 -0
  110. package/src/platform/node/index.ts +17 -0
  111. package/src/platform/node/parser.ts +34 -0
  112. package/src/platform/node/process.ts +51 -0
  113. package/src/platform/runtime.ts +50 -0
  114. package/src/platform/types.ts +150 -0
  115. package/src/prompt/stores/file-store.ts +6 -5
  116. package/src/rag/service.ts +2 -1
  117. package/src/request/response.ts +7 -4
  118. package/src/security/guards/execution-context.ts +4 -4
  119. package/src/security/guards/types.ts +2 -2
  120. package/src/swagger/generator.ts +2 -1
  121. package/src/websocket/registry.ts +6 -7
  122. package/tests/controller/path-combination.test.ts +196 -2
  123. package/tests/files/static-middleware.test.ts +5 -2
  124. package/tests/middleware/static-file.test.ts +5 -2
  125. package/tests/platform/bun/crypto.test.ts +8 -0
  126. package/tests/platform/bun/database.test.ts +8 -0
  127. package/tests/platform/bun/fs.test.ts +8 -0
  128. package/tests/platform/bun/parser.test.ts +8 -0
  129. package/tests/platform/bun/process.test.ts +8 -0
  130. package/tests/platform/bun/websocket.test.ts +8 -0
  131. package/tests/platform/detector.test.ts +57 -0
  132. package/tests/platform/node/build-smoke.test.ts +92 -0
  133. package/tests/platform/node/crypto.test.ts +9 -0
  134. package/tests/platform/node/database.test.ts +9 -0
  135. package/tests/platform/node/fs.test.ts +9 -0
  136. package/tests/platform/node/parser.test.ts +9 -0
  137. package/tests/platform/node/process.test.ts +9 -0
  138. package/tests/platform/node/websocket.test.ts +9 -0
  139. package/tests/platform/shared/crypto.cases.ts +49 -0
  140. package/tests/platform/shared/database.cases.ts +43 -0
  141. package/tests/platform/shared/fs.cases.ts +82 -0
  142. package/tests/platform/shared/parser.cases.ts +55 -0
  143. package/tests/platform/shared/process.cases.ts +26 -0
  144. package/tests/platform/shared/suite.ts +33 -0
  145. package/tests/platform/shared/websocket.cases.ts +61 -0
  146. package/tests/request/response.test.ts +5 -2
  147. package/tests/router/router-extended.test.ts +53 -0
@@ -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
- * 框架内部会转换为 Bun.serve 所需的秒
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
- * 基于 Bun.serve() 构建
64
+ * 基于平台适配层构建,支持 Bun 和 Node.js
64
65
  */
65
66
  export class BunServer {
66
- private server?: Server<WebSocketConnectionData>;
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
- bunServer: Server<WebSocketConnectionData>,
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 — always disable Bun TCP idle timeout for this connection
108
- bunServer.timeout(request, 0);
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
- bunServer: Server<WebSocketConnectionData>,
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 = bunServer.upgrade(request, {
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, bunServer),
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, bunServer);
178
+ return postProcessSse(responsePromise, request, serverHandle);
178
179
  };
179
180
 
180
- const websocketHandlers = {
181
- open: async (ws: import("bun").ServerWebSocket<WebSocketConnectionData>) => {
182
- await this.options.websocketRegistry?.handleOpen(ws);
183
- },
184
- message: async (ws: import("bun").ServerWebSocket<WebSocketConnectionData>, message: string | Buffer) => {
185
- await this.options.websocketRegistry?.handleMessage(ws, message);
186
- },
187
- close: async (ws: import("bun").ServerWebSocket<WebSocketConnectionData>, code: number, reason: string) => {
188
- await this.options.websocketRegistry?.handleClose(ws, code, reason);
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 = Bun.serve({
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
- this.server = Bun.serve({
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
- Bun.write(portFile, String(port));
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(): Server<WebSocketConnectionData> | undefined {
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 = Bun.markdown.html(body.content, { headings: true });
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
  });
@@ -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,22 @@ 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
- // 使用 Bun 原生 SQLite API
184
- const { Database } = await import('bun:sqlite');
185
- const db = new Database(config.path);
186
- return db;
184
+ if (getRuntime().engine === 'bun') {
185
+ const { Database } = await import('bun:sqlite');
186
+ return new Database(config.path);
187
+ }
188
+ // Node.js:使用 better-sqlite3
189
+ const BetterSqlite3 = require('better-sqlite3') as typeof import('better-sqlite3');
190
+ return BetterSqlite3(config.path);
187
191
  }
188
192
 
189
193
  /**
190
- * 创建 PostgreSQL 连接(使用 Bun.SQL)
194
+ * 创建 PostgreSQL 连接(自动感知运行时)
191
195
  */
192
196
  private async createPostgresConnection(
193
197
  config: {
@@ -199,18 +203,18 @@ export class ConnectionPool {
199
203
  ssl?: boolean;
200
204
  },
201
205
  ): Promise<unknown> {
202
- // 使用 Bun.SQL API
203
- // Bun.SQL 支持 postgres:// URL
204
206
  const url = `postgres://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
205
- const { SQL } = await import('bun');
206
- return new SQL(url, {
207
- max: 1, // 单个连接(连接池会在外部管理)
208
- tls: config.ssl ?? false,
209
- });
207
+ if (getRuntime().engine === 'bun') {
208
+ const { SQL } = await import('bun');
209
+ return new SQL(url, { max: 1, tls: config.ssl ?? false });
210
+ }
211
+ // Node.js:使用 postgres 包
212
+ const postgres = require('postgres') as typeof import('postgres');
213
+ return postgres(url, { max: 1, ssl: config.ssl ? 'require' : false });
210
214
  }
211
215
 
212
216
  /**
213
- * 创建 MySQL 连接(使用 Bun.SQL)
217
+ * 创建 MySQL 连接(自动感知运行时)
214
218
  */
215
219
  private async createMysqlConnection(
216
220
  config: {
@@ -221,13 +225,21 @@ export class ConnectionPool {
221
225
  password: string;
222
226
  },
223
227
  ): Promise<unknown> {
224
- // 使用 Bun.SQL API
225
- // Bun.SQL 支持 mysql:// URL
226
- const url = `mysql://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
227
- const { SQL } = await import('bun');
228
- return new SQL(url, {
229
- max: 1, // 单个连接(连接池会在外部管理)
228
+ if (getRuntime().engine === 'bun') {
229
+ const url = `mysql://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
230
+ const { SQL } = await import('bun');
231
+ return new SQL(url, { max: 1 });
232
+ }
233
+ // Node.js:使用 mysql2
234
+ const mysql2 = require('mysql2/promise') as typeof import('mysql2/promise');
235
+ const conn = await mysql2.createConnection({
236
+ host: config.host,
237
+ port: config.port,
238
+ database: config.database,
239
+ user: config.user,
240
+ password: config.password,
230
241
  });
242
+ return conn;
231
243
  }
232
244
 
233
245
  /**
@@ -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;
@@ -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(
@@ -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, SQL>();
10
+ private readonly instances = new Map<string, unknown>();
7
11
  private defaultTenantId = 'default';
8
12
 
9
- public getOrCreate(tenantId: string, config: BunSQLConfig): SQL {
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
- const sql = new SQL(config.url, {
17
- max: pool.max ?? 10,
18
- idleTimeout: pool.idleTimeout ?? 30,
19
- maxLifetime: pool.maxLifetime ?? 0,
20
- connectionTimeout: pool.connectionTimeout ?? 30000,
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): SQL | undefined {
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(): SQL {
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
- await sql.close({ timeout });
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,64 @@ export class Semaphore {
39
38
  }
40
39
  }
41
40
 
41
+ /**
42
+ * SQLite 适配器(自动感知运行时)
43
+ * Bun 平台下使用 bun:sqlite,Node.js 平台下使用 better-sqlite3
44
+ */
42
45
  export class SqliteAdapter {
43
- private readonly db: Database;
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.db = new Database(config.database);
48
- if (config.wal !== false) {
49
- this.db.exec('PRAGMA journal_mode = WAL;');
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
+ const BetterSqlite3 = require('better-sqlite3') as typeof import('better-sqlite3');
62
+ const db = BetterSqlite3(config.database);
63
+ if (config.wal !== false) {
64
+ db.exec('PRAGMA journal_mode = WAL;');
65
+ }
66
+ this.db = db;
50
67
  }
68
+
51
69
  this.semaphore = new Semaphore(config.maxWriteConcurrency ?? 1);
52
70
  }
53
71
 
54
- public query<T = unknown>(sql: string, params: SQLQueryBindings[] = []): T[] {
55
- const stmt = this.db.query(sql);
72
+ public query<T = unknown>(sql: string, params: unknown[] = []): T[] {
73
+ if (this.isBun) {
74
+ const db = this.db as import('bun:sqlite').Database;
75
+ const stmt = db.query(sql);
76
+ return stmt.all(...params as Parameters<typeof stmt.all>) as T[];
77
+ }
78
+ const db = this.db as import('better-sqlite3').Database;
79
+ const stmt = db.prepare(sql);
56
80
  return stmt.all(...params) as T[];
57
81
  }
58
82
 
59
- public async execute(sql: string, params: SQLQueryBindings[] = []): Promise<void> {
60
- const stmt = this.db.query(sql);
61
- stmt.run(...params);
83
+ public async execute(sql: string, params: unknown[] = []): Promise<void> {
84
+ if (this.isBun) {
85
+ const db = this.db as import('bun:sqlite').Database;
86
+ const stmt = db.query(sql);
87
+ stmt.run(...params as Parameters<typeof stmt.run>);
88
+ } else {
89
+ const db = this.db as import('better-sqlite3').Database;
90
+ const stmt = db.prepare(sql);
91
+ stmt.run(...params);
92
+ }
62
93
  }
63
94
 
64
95
  public close(): void {
65
- this.db.close();
96
+ if (typeof (this.db as any).close === 'function') {
97
+ (this.db as any).close();
98
+ }
66
99
  }
67
100
  }
68
101
 
@@ -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 records = Bun.JSONL.parse(content) as RequestRecord[];
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
- return Bun.JSONL.parse(content) as RequestRecord[];
139
+ const { getRuntime } = require('../platform/runtime') as typeof import('../platform/runtime');
140
+ return getRuntime().parser.parseJSONL(content) as RequestRecord[];
140
141
  }
141
142
  }
@@ -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 = Bun.file(filePath);
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
  },
@@ -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 Bun.write(targetPath, file);
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 type { HeadersInit } from 'bun'
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?: HeadersInit | 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 = Bun.file(targetPath);
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
+ };