@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.
- 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-pool.d.ts +3 -3
- package/dist/database/connection-pool.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 +40264 -3542
- package/dist/index.node.mjs +17689 -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/design/query-interceptor-design.md +381 -0
- 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 -6
- 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-pool.ts +32 -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/sql-manager.ts +48 -13
- package/src/database/sqlite-adapter.ts +45 -12
- 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
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# 平台适配指南
|
|
2
|
+
|
|
3
|
+
[English](../platform.md) | **中文**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Bun Server 通过内部的 Platform Adapter Layer,原生支持在 **Bun** 和 **Node.js 22+** 上运行。所有运行时相关的 API(HTTP 服务器、文件 I/O、加密、解析器、进程管理、WebSocket)均被抽象到统一的 TypeScript 接口后。你编写一套代码,框架在启动时自动选择正确的实现——无需额外配置。
|
|
8
|
+
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
- [架构](#架构)
|
|
12
|
+
- [运行时检测](#运行时检测)
|
|
13
|
+
- [平台配置](#平台配置)
|
|
14
|
+
- [支持矩阵](#支持矩阵)
|
|
15
|
+
- [数据库自动适配](#数据库自动适配)
|
|
16
|
+
- [公开 API 变更](#公开-api-变更)
|
|
17
|
+
- [Bun 独有特性](#bun-独有特性)
|
|
18
|
+
- [Node.js 启动指南](#nodejs-启动指南)
|
|
19
|
+
- [多运行时测试](#多运行时测试)
|
|
20
|
+
- [已知限制](#已知限制)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 架构
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌──────────────────────────────────────────────────────┐
|
|
28
|
+
│ 应用层 │
|
|
29
|
+
│ Controllers / Services / Modules / Middleware │
|
|
30
|
+
└──────────────────────────┬───────────────────────────┘
|
|
31
|
+
│ getRuntime()
|
|
32
|
+
┌──────────────────────────▼───────────────────────────┐
|
|
33
|
+
│ Platform Adapter Layer │
|
|
34
|
+
│ IFsAdapter · ICryptoAdapter · IParserAdapter │
|
|
35
|
+
│ IProcessAdapter · IHttpDriver · IWebSocket │
|
|
36
|
+
└──────┬───────────────────────────────┬───────────────┘
|
|
37
|
+
│ │
|
|
38
|
+
┌──────▼──────┐ ┌──────▼──────┐
|
|
39
|
+
│ BunPlatform │ │ NodePlatform│
|
|
40
|
+
│ Bun.serve │ │ node:http │
|
|
41
|
+
│ Bun.file │ │ node:fs │
|
|
42
|
+
│ Bun.Crypto │ │ node:crypto │
|
|
43
|
+
│ spawn(bun) │ │ ws package │
|
|
44
|
+
└─────────────┘ └─────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
每个适配器接口及其对应实现:
|
|
48
|
+
|
|
49
|
+
| 接口 | Bun 实现 | Node.js 实现 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `IFsAdapter` | `Bun.file`、`Bun.write`、`Bun.Glob` | `node:fs/promises`、`node:fs` glob |
|
|
52
|
+
| `ICryptoAdapter` | `Bun.CryptoHasher` | `node:crypto` HMAC/hash |
|
|
53
|
+
| `IParserAdapter` | `Bun.JSONC`、`Bun.JSON5`、`Bun.JSONL`、`Bun.markdown` | `jsonc-parser`、`json5`、自定义 JSONL、`marked` |
|
|
54
|
+
| `IProcessAdapter` | `spawn`(bun)、`Bun.sleep` | `node:child_process`、`setTimeout` |
|
|
55
|
+
| `IHttpDriver` | `Bun.serve` | `node:http.createServer` |
|
|
56
|
+
| `IWebSocket<T>` | `Bun.ServerWebSocket<T>` | `ws` 包 |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 运行时检测
|
|
61
|
+
|
|
62
|
+
平台在 `Application` 构造时按以下优先级链解析:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
1. Bootstrap 配置 → new Application({ platform: 'node' })
|
|
66
|
+
2. CLI 参数 → --platform=node
|
|
67
|
+
3. 环境变量 → BUN_SERVER_PLATFORM=node
|
|
68
|
+
4. 自动检测 → typeof Bun !== 'undefined' ? 'bun' : 'node'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
一旦解析完成,单例在进程生命周期内保持不变。后续调用 `getRuntime()` 始终返回同一实例。
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 平台配置
|
|
76
|
+
|
|
77
|
+
### 方式一 — 代码配置(最高优先级)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { Application } from '@dangao/bun-server';
|
|
81
|
+
|
|
82
|
+
const app = new Application({ platform: 'node' }); // 'bun' | 'node'
|
|
83
|
+
app.registerModule(AppModule);
|
|
84
|
+
await app.listen(3000);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 方式二 — CLI 参数
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Bun 运行时,但强制使用 Node.js 适配器
|
|
91
|
+
bun run src/main.ts --platform=node
|
|
92
|
+
|
|
93
|
+
# Node.js 运行时(无需参数,自动检测)
|
|
94
|
+
node dist/main.js
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 方式三 — 环境变量
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
BUN_SERVER_PLATFORM=node node dist/main.js
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 方式四 — 自动检测(默认,无需配置)
|
|
104
|
+
|
|
105
|
+
无需任何配置。如果 `typeof Bun !== 'undefined'`,选择 `BunPlatform`;否则自动选择 `NodePlatform`。
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 支持矩阵
|
|
110
|
+
|
|
111
|
+
| 特性 | Bun | Node.js 22+ |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| HTTP 服务器 | `Bun.serve`(原生) | `node:http` |
|
|
114
|
+
| WebSocket | `Bun.ServerWebSocket`(原生) | `ws` 包 |
|
|
115
|
+
| 文件 I/O | `Bun.file / Bun.write` | `node:fs/promises` |
|
|
116
|
+
| Crypto / JWT | `Bun.CryptoHasher` | `node:crypto` |
|
|
117
|
+
| JSONC 解析 | `Bun.JSONC` | `jsonc-parser` 包 |
|
|
118
|
+
| JSON5 解析 | `Bun.JSON5` | `json5` 包 |
|
|
119
|
+
| JSONL 解析 | `Bun.JSONL` | 自定义流式解析器 |
|
|
120
|
+
| Markdown 渲染 | `Bun.markdown` | `marked` 包 |
|
|
121
|
+
| Cluster spawn | Bun `spawn` | `node:child_process` |
|
|
122
|
+
| SQLite | `bun:sqlite` | `better-sqlite3` |
|
|
123
|
+
| PostgreSQL | `Bun.SQL` | `postgres` 包 |
|
|
124
|
+
| MySQL | `Bun.SQL` | `mysql2` 包 |
|
|
125
|
+
| `idleTimeout` | 支持 | 静默忽略 |
|
|
126
|
+
| `reusePort` | 支持 | 静默忽略 |
|
|
127
|
+
| SSE TCP keepalive | 支持(via `server.timeout`) | 不可用 |
|
|
128
|
+
| 整体性能 | 最优 | 良好 |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 数据库自动适配
|
|
133
|
+
|
|
134
|
+
`DatabaseModule` 根据检测到的平台自动选择数据库驱动,**无需用户额外配置**。
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Bun 平台 Node.js 平台
|
|
138
|
+
───────────────────────────── ─────────────────────────────
|
|
139
|
+
SQLite → bun:sqlite SQLite → better-sqlite3
|
|
140
|
+
PostgreSQL → Bun.SQL PostgreSQL → postgres 包
|
|
141
|
+
MySQL → Bun.SQL MySQL → mysql2 包
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
你的 `DatabaseModule` 配置在两个平台间完全一致:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
DatabaseModule.forRoot({
|
|
148
|
+
connections: [
|
|
149
|
+
{
|
|
150
|
+
name: 'default',
|
|
151
|
+
type: 'sqlite',
|
|
152
|
+
database: './data/app.db',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'pg',
|
|
156
|
+
type: 'postgres',
|
|
157
|
+
url: process.env.DATABASE_URL,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 公开 API 变更
|
|
166
|
+
|
|
167
|
+
### `BunServer.getServer()`
|
|
168
|
+
|
|
169
|
+
返回 `IServerHandle | undefined`(原来为 `Bun.Server | undefined`)。
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const handle: IServerHandle | undefined = app.getServer();
|
|
173
|
+
handle?.port; // number
|
|
174
|
+
handle?.hostname; // string
|
|
175
|
+
await handle?.stop();
|
|
176
|
+
|
|
177
|
+
// 访问底层原生服务器实例(不推荐,类型为 unknown)
|
|
178
|
+
const native: unknown = app.getNativeServer();
|
|
179
|
+
// Bun: native as Bun.Server<any>
|
|
180
|
+
// Node.js: native as import('node:http').Server
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `WsArgumentsHost.getClient()`
|
|
184
|
+
|
|
185
|
+
返回 `IWebSocket<T>` 而非 Bun 的 `ServerWebSocket<T>`。这是 WebSocket 公开 API 中的**唯一破坏性变更**。
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import type { IWebSocket } from '@dangao/bun-server';
|
|
189
|
+
|
|
190
|
+
@WebSocketGateway()
|
|
191
|
+
class ChatGateway {
|
|
192
|
+
@OnMessage('chat')
|
|
193
|
+
onChat(client: IWebSocket<unknown>, data: unknown) {
|
|
194
|
+
client.send('pong');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`IWebSocket<T>` 暴露与 `ServerWebSocket<T>` 相同的核心方法:
|
|
200
|
+
`send`、`close`、`data`、`readyState`、`remoteAddress`。
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Bun 独有特性
|
|
205
|
+
|
|
206
|
+
以下选项在 `ApplicationOptions` 中接受,但仅在 Bun 上生效,在 Node.js 上静默忽略。
|
|
207
|
+
|
|
208
|
+
| 选项 | Bun 效果 | Node.js |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `idleTimeout` | 通过 `Bun.serve` 设置 TCP 空闲超时 | 忽略 |
|
|
211
|
+
| `reusePort` | 通过 `Bun.serve` 启用端口复用 | 忽略 |
|
|
212
|
+
| `sseKeepAlive` | 使用 `server.timeout(req, 0)` | 仅注入心跳 |
|
|
213
|
+
|
|
214
|
+
如果你的应用依赖这些特性,请在文档中明确说明对 Bun 运行时的要求。
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Node.js 启动指南
|
|
219
|
+
|
|
220
|
+
### 1. 安装
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm install @dangao/bun-server
|
|
224
|
+
# Node.js 所需的对等依赖会自动安装
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 2. 构建
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# 使用 bun build 输出面向 Node.js 的 JS 文件
|
|
231
|
+
bun build src/main.ts --target=node --outdir=dist
|
|
232
|
+
|
|
233
|
+
# 或使用 tsc
|
|
234
|
+
npx tsc
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 3. 运行
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
node dist/main.js
|
|
241
|
+
# 平台自动检测为 'node',无需额外参数
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 4. 冒烟测试(验证 bun build 输出能被 Node.js 原生运行)
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
bun run test:node
|
|
248
|
+
# 等价于:vitest run tests/platform/node
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 多运行时测试
|
|
254
|
+
|
|
255
|
+
共享测试用例位于 `tests/platform/shared/*.cases.ts`。Bun 和 Node.js 的测试运行器导入相同的断言,保证测试覆盖完全一致。
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
tests/platform/
|
|
259
|
+
├── shared/
|
|
260
|
+
│ ├── suite.ts ← TestSuite 接口(test / expect / beforeEach)
|
|
261
|
+
│ ├── fs.cases.ts
|
|
262
|
+
│ ├── crypto.cases.ts
|
|
263
|
+
│ ├── parser.cases.ts
|
|
264
|
+
│ ├── process.cases.ts
|
|
265
|
+
│ ├── websocket.cases.ts
|
|
266
|
+
│ └── database.cases.ts
|
|
267
|
+
├── bun/ ← bun:test 运行器
|
|
268
|
+
│ └── *.test.ts
|
|
269
|
+
├── node/ ← vitest 运行器
|
|
270
|
+
│ ├── *.test.ts
|
|
271
|
+
│ └── build-smoke.test.ts ← 验证 bun build --target=node 输出
|
|
272
|
+
└── detector.test.ts ← 优先级链单元测试
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 执行测试
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# Bun 平台测试
|
|
279
|
+
bun run test:bun
|
|
280
|
+
|
|
281
|
+
# Node.js 平台测试
|
|
282
|
+
bun run test:node
|
|
283
|
+
|
|
284
|
+
# 两个平台全部测试
|
|
285
|
+
bun run test:platform
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 已知限制
|
|
291
|
+
|
|
292
|
+
| 限制 | 说明 |
|
|
293
|
+
|---|---|
|
|
294
|
+
| `idleTimeout` / `reusePort` | Bun 独有,Node.js 无对等实现 |
|
|
295
|
+
| SSE TCP keepalive(`server.timeout`) | Bun 独有 API;Node.js 仅支持心跳注入 |
|
|
296
|
+
| `Bun.SQL` 高级特性 | 部分 Bun.SQL 选项(如预处理语句缓存)在 `postgres`/`mysql2` 中不可用 |
|
|
297
|
+
| Node.js 上的 Cluster 模式 | 使用 `node:child_process`,行为可能与 Bun cluster 存在差异 |
|
|
298
|
+
| `bun:sqlite` 扩展 | Bun SQLite 支持扩展加载;`better-sqlite3` 的扩展机制不同 |
|
|
299
|
+
| 整体性能 | Bun 原生 API 在文件 I/O 和加密等方面性能优于 Node.js 适配实现 |
|
package/docs/zh/testing.md
CHANGED
|
@@ -61,6 +61,66 @@ const client = await module.createHttpClient();
|
|
|
61
61
|
|
|
62
62
|
`options` 支持 `headers`、`body`、`query`。响应对象包含 `status`、`headers`、`body`、`text`、`ok`。
|
|
63
63
|
|
|
64
|
+
## 多运行时测试
|
|
65
|
+
|
|
66
|
+
框架内置的共享测试策略,能在 Bun 和 Node.js 上运行完全相同的测试用例。
|
|
67
|
+
|
|
68
|
+
### 测试目录结构
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
tests/platform/
|
|
72
|
+
├── shared/ ← 平台无关的断言辅助函数
|
|
73
|
+
│ ├── suite.ts ← TestSuite 接口(test / expect / beforeEach)
|
|
74
|
+
│ ├── fs.cases.ts
|
|
75
|
+
│ ├── crypto.cases.ts
|
|
76
|
+
│ ├── parser.cases.ts
|
|
77
|
+
│ ├── process.cases.ts
|
|
78
|
+
│ ├── websocket.cases.ts
|
|
79
|
+
│ └── database.cases.ts
|
|
80
|
+
├── bun/ ← bun:test 运行器(initRuntime('bun'))
|
|
81
|
+
│ └── *.test.ts
|
|
82
|
+
└── node/ ← vitest 运行器(initRuntime('node'))
|
|
83
|
+
├── *.test.ts
|
|
84
|
+
└── build-smoke.test.ts
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 执行平台测试
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 仅 Bun 平台测试
|
|
91
|
+
bun run test:bun
|
|
92
|
+
|
|
93
|
+
# 仅 Node.js 平台测试(使用 vitest)
|
|
94
|
+
bun run test:node
|
|
95
|
+
|
|
96
|
+
# 两个平台全部测试
|
|
97
|
+
bun run test:platform
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 编写跨平台测试
|
|
101
|
+
|
|
102
|
+
在测试文件开头调用 `initRuntime()`:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// tests/platform/bun/fs.test.ts
|
|
106
|
+
import { test, expect, beforeEach } from 'bun:test';
|
|
107
|
+
import { initRuntime, _resetRuntime } from '../../../src/platform/runtime';
|
|
108
|
+
import { runFsCases } from '../shared/fs.cases';
|
|
109
|
+
|
|
110
|
+
initRuntime('bun');
|
|
111
|
+
runFsCases({ test, expect, beforeEach });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// tests/platform/node/fs.test.ts
|
|
116
|
+
import { test, expect, beforeEach } from 'vitest';
|
|
117
|
+
import { initRuntime, _resetRuntime } from '../../../src/platform/runtime';
|
|
118
|
+
import { runFsCases } from '../shared/fs.cases';
|
|
119
|
+
|
|
120
|
+
beforeEach(() => { _resetRuntime(); initRuntime('node'); });
|
|
121
|
+
runFsCases({ test, expect, beforeEach });
|
|
122
|
+
```
|
|
123
|
+
|
|
64
124
|
## 配合 bun:test 使用
|
|
65
125
|
|
|
66
126
|
```ts
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dangao/bun-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
+
"node": "./dist/index.node.mjs",
|
|
9
10
|
"types": "./dist/index.d.ts",
|
|
10
11
|
"import": "./dist/index.js",
|
|
11
12
|
"default": "./dist/index.js"
|
|
@@ -38,27 +39,44 @@
|
|
|
38
39
|
"url": "https://github.com/dangaogit/bun-server/issues"
|
|
39
40
|
},
|
|
40
41
|
"engines": {
|
|
41
|
-
"bun": ">=1.3.10"
|
|
42
|
+
"bun": ">=1.3.10",
|
|
43
|
+
"node": ">=22.0.0"
|
|
42
44
|
},
|
|
43
45
|
"scripts": {
|
|
44
46
|
"dev": "bun --watch src/index.ts",
|
|
45
47
|
"test": "bun test",
|
|
48
|
+
"test:bun": "bun test tests/platform/bun",
|
|
49
|
+
"test:node": "vitest run tests/platform/node",
|
|
50
|
+
"test:platform": "bun run test:bun && bun run test:node",
|
|
46
51
|
"type-check": "tsc --noEmit",
|
|
47
52
|
"clean": "rm -rf dist",
|
|
48
53
|
"bundle": "bun build src/index.ts --target=bun --format=esm --outfile=dist/index.js --external reflect-metadata --external @dangao/logsmith --external @dangao/nacos-client",
|
|
54
|
+
"bundle:node": "bun build src/index.ts --target=node --packages=external --outfile=dist/index.node.mjs",
|
|
49
55
|
"dts": "tsc -p tsconfig.build.json",
|
|
50
|
-
"build": "bun run clean && bun run bundle && bun run dts",
|
|
56
|
+
"build": "bun run clean && bun run bundle && bun run bundle:node && bun run dts",
|
|
51
57
|
"prepublishOnly": "bun run build",
|
|
52
58
|
"publish:package": "cp -r ../../README.md ../../LICENSE ../../docs . && bun publish --access public && rm -rf ./README.md ./LICENSE ./docs",
|
|
53
59
|
"publish:package:github": "cp -r ../../README.md ../../LICENSE ../../docs . && bun pm pack --access public && npm publish *.tgz --provenance --access public && rm -rf ./README.md ./LICENSE ./docs"
|
|
54
60
|
},
|
|
55
61
|
"devDependencies": {
|
|
62
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
63
|
+
"@types/bun": "^1.3.10",
|
|
64
|
+
"@types/mime-types": "^3.0.1",
|
|
65
|
+
"@types/ws": "^8.18.1",
|
|
56
66
|
"typescript": "^5.9.3",
|
|
57
|
-
"
|
|
67
|
+
"vitest": "^4.1.4"
|
|
58
68
|
},
|
|
59
69
|
"dependencies": {
|
|
60
|
-
"reflect-metadata": "^0.2.2",
|
|
61
70
|
"@dangao/logsmith": "0.2.0",
|
|
62
|
-
"@dangao/nacos-client": "0.1.1"
|
|
71
|
+
"@dangao/nacos-client": "0.1.1",
|
|
72
|
+
"better-sqlite3": "^12.8.0",
|
|
73
|
+
"json5": "^2.2.3",
|
|
74
|
+
"jsonc-parser": "^3.3.1",
|
|
75
|
+
"marked": "^18.0.0",
|
|
76
|
+
"mime-types": "^3.0.2",
|
|
77
|
+
"mysql2": "^3.21.0",
|
|
78
|
+
"postgres": "^3.4.9",
|
|
79
|
+
"reflect-metadata": "^0.2.2",
|
|
80
|
+
"ws": "^8.20.0"
|
|
63
81
|
}
|
|
64
82
|
}
|
package/src/auth/jwt.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JWTConfig, JWTPayload } from './types';
|
|
2
|
+
import { getRuntime } from '../platform/runtime';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* JWT 工具类
|
|
@@ -120,7 +121,7 @@ export class JWTUtil {
|
|
|
120
121
|
|
|
121
122
|
if (key.length > blockSize) {
|
|
122
123
|
// 如果密钥长度超过块大小,先哈希
|
|
123
|
-
const hasher =
|
|
124
|
+
const hasher = getRuntime().crypto.createHasher('sha256');
|
|
124
125
|
hasher.update(key);
|
|
125
126
|
keyBuffer = new Uint8Array(hasher.digest());
|
|
126
127
|
} else {
|
|
@@ -141,7 +142,7 @@ export class JWTUtil {
|
|
|
141
142
|
const innerData = new Uint8Array(iKeyPad.length + data.length);
|
|
142
143
|
innerData.set(iKeyPad);
|
|
143
144
|
innerData.set(data, iKeyPad.length);
|
|
144
|
-
const innerHasher =
|
|
145
|
+
const innerHasher = getRuntime().crypto.createHasher('sha256');
|
|
145
146
|
innerHasher.update(innerData);
|
|
146
147
|
const innerHash = new Uint8Array(innerHasher.digest());
|
|
147
148
|
|
|
@@ -149,7 +150,7 @@ export class JWTUtil {
|
|
|
149
150
|
const outerData = new Uint8Array(oKeyPad.length + innerHash.length);
|
|
150
151
|
outerData.set(oKeyPad);
|
|
151
152
|
outerData.set(innerHash, oKeyPad.length);
|
|
152
|
-
const outerHasher =
|
|
153
|
+
const outerHasher = getRuntime().crypto.createHasher('sha256');
|
|
153
154
|
outerHasher.update(outerData);
|
|
154
155
|
|
|
155
156
|
return new Uint8Array(outerHasher.digest());
|
package/src/config/service.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ConfigFileFormat } from './types';
|
|
2
|
+
import { getRuntime } from '../platform/runtime';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 配置服务
|
|
@@ -7,16 +8,16 @@ import type { ConfigFileFormat } from './types';
|
|
|
7
8
|
export class ConfigService<TConfig extends Record<string, unknown> = Record<string, unknown>> {
|
|
8
9
|
/**
|
|
9
10
|
* 解析配置内容,按 JSON -> JSONC -> JSON5 顺序自动尝试
|
|
10
|
-
* 利用 Bun 1.3.6+ 的 Bun.JSONC 和 Bun 1.3.7+ 的 Bun.JSON5
|
|
11
11
|
* @param content - 配置文本内容
|
|
12
12
|
* @param format - 强制指定格式(可选),省略则自动检测
|
|
13
13
|
*/
|
|
14
14
|
public static parseConfigContent(content: string, format?: ConfigFileFormat): unknown {
|
|
15
|
+
const parser = getRuntime().parser;
|
|
15
16
|
if (format === 'jsonc') {
|
|
16
|
-
return
|
|
17
|
+
return parser.parseJSONC(content);
|
|
17
18
|
}
|
|
18
19
|
if (format === 'json5') {
|
|
19
|
-
return
|
|
20
|
+
return parser.parseJSON5(content);
|
|
20
21
|
}
|
|
21
22
|
if (format === 'json') {
|
|
22
23
|
return JSON.parse(content);
|
|
@@ -26,9 +27,9 @@ export class ConfigService<TConfig extends Record<string, unknown> = Record<stri
|
|
|
26
27
|
return JSON.parse(content);
|
|
27
28
|
} catch (_error) {
|
|
28
29
|
try {
|
|
29
|
-
return
|
|
30
|
+
return parser.parseJSONC(content);
|
|
30
31
|
} catch (_innerError) {
|
|
31
|
-
return
|
|
32
|
+
return parser.parseJSON5(content);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -38,7 +39,7 @@ export class ConfigService<TConfig extends Record<string, unknown> = Record<stri
|
|
|
38
39
|
* @param filePath - 配置文件路径(.json / .jsonc / .json5)
|
|
39
40
|
*/
|
|
40
41
|
public static async loadConfigFile(filePath: string): Promise<Record<string, unknown>> {
|
|
41
|
-
const file =
|
|
42
|
+
const file = getRuntime().fs.file(filePath);
|
|
42
43
|
const content = await file.text();
|
|
43
44
|
|
|
44
45
|
let format: ConfigFileFormat | undefined;
|
package/src/core/application.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { LoggerManager } from '@dangao/logsmith';
|
|
|
21
21
|
import { EventModule } from '../events/event-module';
|
|
22
22
|
import { AsyncProviderRegistry } from '../di/async-module';
|
|
23
23
|
import { ServiceRegistryModule } from '../microservice/service-registry/service-registry-module';
|
|
24
|
+
import type { PlatformEngine } from '../platform/types';
|
|
25
|
+
import { initRuntime } from '../platform/runtime';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* 应用配置选项
|
|
@@ -79,6 +81,19 @@ export interface ApplicationOptions {
|
|
|
79
81
|
/** 心跳间隔(毫秒),默认 15000 */
|
|
80
82
|
intervalMs?: number;
|
|
81
83
|
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 运行时平台选择
|
|
87
|
+
*
|
|
88
|
+
* 优先级(从高到低):
|
|
89
|
+
* 1. 此字段(构造函数选项)
|
|
90
|
+
* 2. CLI 参数 `--platform=node`
|
|
91
|
+
* 3. 环境变量 `BUN_SERVER_PLATFORM=node`
|
|
92
|
+
* 4. 自动检测(有 Bun 全局对象则使用 bun,否则使用 node)
|
|
93
|
+
*
|
|
94
|
+
* @default 自动检测
|
|
95
|
+
*/
|
|
96
|
+
platform?: PlatformEngine;
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
/**
|
|
@@ -94,6 +109,9 @@ export class Application {
|
|
|
94
109
|
private signalHandlersInstalled: boolean = false;
|
|
95
110
|
|
|
96
111
|
public constructor(options: ApplicationOptions = {}) {
|
|
112
|
+
// Initialize platform runtime first — must be the very first operation
|
|
113
|
+
initRuntime(options.platform);
|
|
114
|
+
|
|
97
115
|
this.options = options;
|
|
98
116
|
this.middlewarePipeline = new MiddlewarePipeline([createErrorHandlingMiddleware()]);
|
|
99
117
|
this.websocketRegistry = WebSocketGatewayRegistry.getInstance();
|
|
@@ -175,7 +193,7 @@ export class Application {
|
|
|
175
193
|
};
|
|
176
194
|
|
|
177
195
|
this.server = new BunServer(serverOptions);
|
|
178
|
-
this.server.start();
|
|
196
|
+
await this.server.start();
|
|
179
197
|
|
|
180
198
|
// 安装信号处理器(如果启用)
|
|
181
199
|
if (this.options.enableSignalHandlers !== false) {
|
package/src/core/cluster.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { spawn } from 'bun';
|
|
2
1
|
import { LoggerManager } from '@dangao/logsmith';
|
|
3
2
|
import { tmpdir } from 'os';
|
|
4
3
|
import { join } from 'path';
|
|
5
4
|
import { mkdirSync, rmSync, existsSync } from 'fs';
|
|
5
|
+
import type { IServerHandle, IChildProcess } from '../platform/types';
|
|
6
|
+
import { getRuntime } from '../platform/runtime';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 集群模式
|
|
@@ -38,12 +39,12 @@ export interface ClusterOptions {
|
|
|
38
39
|
*/
|
|
39
40
|
export class ClusterManager {
|
|
40
41
|
private readonly workerCount: number;
|
|
41
|
-
private readonly workers:
|
|
42
|
+
private readonly workers: IChildProcess[] = [];
|
|
42
43
|
private readonly scriptPath: string;
|
|
43
44
|
private readonly port: number;
|
|
44
45
|
private readonly hostname?: string;
|
|
45
46
|
private readonly mode: 'reusePort' | 'proxy';
|
|
46
|
-
private proxyServer?:
|
|
47
|
+
private proxyServer?: IServerHandle;
|
|
47
48
|
private socketPaths: string[] = [];
|
|
48
49
|
private roundRobinIndex = 0;
|
|
49
50
|
private socketDir?: string;
|
|
@@ -145,8 +146,8 @@ export class ClusterManager {
|
|
|
145
146
|
this.monitorReusePortWorkers();
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
private spawnReusePortWorker(index: number):
|
|
149
|
-
return spawn({
|
|
149
|
+
private spawnReusePortWorker(index: number): IChildProcess {
|
|
150
|
+
return getRuntime().process.spawn({
|
|
150
151
|
cmd: ['bun', 'run', this.scriptPath],
|
|
151
152
|
env: {
|
|
152
153
|
...process.env,
|
|
@@ -197,7 +198,7 @@ export class ClusterManager {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
await this.waitForWorkerSockets();
|
|
200
|
-
this.startProxyServer();
|
|
201
|
+
await this.startProxyServer();
|
|
201
202
|
|
|
202
203
|
logger.info(
|
|
203
204
|
`[Cluster] ${this.workerCount} workers ready (proxy mode, unix sockets)`,
|
|
@@ -206,8 +207,8 @@ export class ClusterManager {
|
|
|
206
207
|
this.monitorProxyWorkers();
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
private spawnProxyWorker(index: number, socketPath: string):
|
|
210
|
-
return spawn({
|
|
210
|
+
private spawnProxyWorker(index: number, socketPath: string): IChildProcess {
|
|
211
|
+
return getRuntime().process.spawn({
|
|
211
212
|
cmd: ['bun', 'run', this.scriptPath],
|
|
212
213
|
env: {
|
|
213
214
|
...process.env,
|
|
@@ -239,11 +240,11 @@ export class ClusterManager {
|
|
|
239
240
|
|
|
240
241
|
if (allReady) {
|
|
241
242
|
// Give workers a moment to finish bind+listen after file creation
|
|
242
|
-
await
|
|
243
|
+
await getRuntime().process.sleep(200);
|
|
243
244
|
return;
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
await
|
|
247
|
+
await getRuntime().process.sleep(100);
|
|
247
248
|
}
|
|
248
249
|
|
|
249
250
|
const ready = this.socketPaths.filter((p) => existsSync(p)).length;
|
|
@@ -252,11 +253,11 @@ export class ClusterManager {
|
|
|
252
253
|
);
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
private startProxyServer(): void {
|
|
256
|
+
private async startProxyServer(): Promise<void> {
|
|
256
257
|
const sockets = this.socketPaths;
|
|
257
258
|
const count = sockets.length;
|
|
258
259
|
|
|
259
|
-
this.proxyServer =
|
|
260
|
+
this.proxyServer = await getRuntime().http.serve({
|
|
260
261
|
port: this.port,
|
|
261
262
|
hostname: this.hostname,
|
|
262
263
|
fetch: async (req) => {
|
|
@@ -269,6 +270,7 @@ export class ClusterManager {
|
|
|
269
270
|
headers: req.headers,
|
|
270
271
|
body: req.body,
|
|
271
272
|
redirect: 'manual',
|
|
273
|
+
// @ts-ignore — unix fetch is Bun-specific; Node fallback skips this
|
|
272
274
|
unix: socketPath,
|
|
273
275
|
});
|
|
274
276
|
} catch (_error) {
|
|
@@ -296,11 +298,11 @@ export class ClusterManager {
|
|
|
296
298
|
const deadline = Date.now() + 15_000;
|
|
297
299
|
while (Date.now() < deadline) {
|
|
298
300
|
if (existsSync(socketPath)) {
|
|
299
|
-
await
|
|
301
|
+
await getRuntime().process.sleep(200);
|
|
300
302
|
logger.info(`[Cluster] Worker ${index} restarted (unix socket)`);
|
|
301
303
|
return;
|
|
302
304
|
}
|
|
303
|
-
await
|
|
305
|
+
await getRuntime().process.sleep(100);
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
logger.error(`[Cluster] Worker ${index} failed to restart within 15s`);
|