vafast 0.2.4 → 0.3.2
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 +296 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/node-server/index.d.ts +22 -0
- package/dist/node-server/index.js +21 -0
- package/dist/node-server/request.d.ts +11 -0
- package/dist/node-server/request.js +157 -0
- package/dist/node-server/response.d.ts +15 -0
- package/dist/node-server/response.js +98 -0
- package/dist/node-server/serve.d.ts +52 -0
- package/dist/node-server/serve.js +88 -0
- package/dist/serve.d.ts +12 -0
- package/dist/serve.js +11 -0
- package/dist/server/server.js +2 -1
- package/dist/utils/parsers.js +4 -3
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
**超高性能的 TypeScript Web 框架,类型安全、轻量、快速。**
|
|
8
8
|
|
|
9
|
+
> Vafast 不只是框架,更是一种 **结构、清晰、可控** 的开发哲学。
|
|
10
|
+
|
|
9
11
|
```typescript
|
|
10
12
|
import { Server, createHandler } from 'vafast';
|
|
11
13
|
|
|
@@ -44,6 +46,291 @@ npm install vafast
|
|
|
44
46
|
bun add vafast
|
|
45
47
|
```
|
|
46
48
|
|
|
49
|
+
## 💡 设计哲学
|
|
50
|
+
|
|
51
|
+
### 结构即真相 — 无装饰器,无链式魔法
|
|
52
|
+
|
|
53
|
+
**Elysia 完整示例:**
|
|
54
|
+
```typescript
|
|
55
|
+
import { Elysia } from 'elysia';
|
|
56
|
+
|
|
57
|
+
const app = new Elysia()
|
|
58
|
+
.get('/users', () => 'list users')
|
|
59
|
+
.post('/users', ({ body }) => body)
|
|
60
|
+
.get('/users/:id', ({ params }) => `User ${params.id}`)
|
|
61
|
+
.use(somePlugin); // 插件作用域?要看文档
|
|
62
|
+
|
|
63
|
+
export default app;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Hono 完整示例:**
|
|
67
|
+
```typescript
|
|
68
|
+
import { Hono } from 'hono';
|
|
69
|
+
|
|
70
|
+
const app = new Hono();
|
|
71
|
+
app.get('/users', (c) => c.text('list users'));
|
|
72
|
+
app.post('/users', async (c) => c.json(await c.req.json()));
|
|
73
|
+
app.get('/users/:id', (c) => c.text(`User ${c.req.param('id')}`));
|
|
74
|
+
|
|
75
|
+
export default app;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Vafast 完整示例:**
|
|
79
|
+
```typescript
|
|
80
|
+
import { Server, createHandler } from 'vafast';
|
|
81
|
+
import type { Route } from 'vafast';
|
|
82
|
+
|
|
83
|
+
const routes: Route[] = [
|
|
84
|
+
{ method: 'GET', path: '/users', handler: createHandler(() => 'list users') },
|
|
85
|
+
{ method: 'POST', path: '/users', handler: createHandler(({ body }) => body) },
|
|
86
|
+
{ method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => `User ${params.id}`) },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const server = new Server(routes);
|
|
90
|
+
export default { fetch: server.fetch };
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**对比:Vafast 的路由是一个数组,一眼看清所有 API 端点。**
|
|
94
|
+
|
|
95
|
+
### 错误即数据 — 不是混乱,是契约
|
|
96
|
+
|
|
97
|
+
**Hono 完整示例:**
|
|
98
|
+
```typescript
|
|
99
|
+
import { Hono } from 'hono';
|
|
100
|
+
import { HTTPException } from 'hono/http-exception';
|
|
101
|
+
|
|
102
|
+
const app = new Hono();
|
|
103
|
+
|
|
104
|
+
app.get('/user', (c) => {
|
|
105
|
+
const name = c.req.query('name');
|
|
106
|
+
if (!name) {
|
|
107
|
+
throw new HTTPException(400, { message: 'Missing name' });
|
|
108
|
+
// 响应格式自己定,没有标准
|
|
109
|
+
}
|
|
110
|
+
return c.text(`Hello, ${name}`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export default app;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Vafast 完整示例:**
|
|
117
|
+
```typescript
|
|
118
|
+
import { Server, VafastError, createHandler } from 'vafast';
|
|
119
|
+
import type { Route } from 'vafast';
|
|
120
|
+
|
|
121
|
+
const routes: Route[] = [
|
|
122
|
+
{
|
|
123
|
+
method: 'GET',
|
|
124
|
+
path: '/user',
|
|
125
|
+
handler: createHandler((ctx) => {
|
|
126
|
+
const name = ctx.query.name;
|
|
127
|
+
if (!name) {
|
|
128
|
+
throw new VafastError('Missing name', {
|
|
129
|
+
status: 400,
|
|
130
|
+
type: 'bad_request',
|
|
131
|
+
expose: true, // 控制是否暴露给客户端
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return `Hello, ${name}`;
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
const server = new Server(routes);
|
|
140
|
+
export default { fetch: server.fetch };
|
|
141
|
+
// 错误响应: { type: 'bad_request', message: 'Missing name' }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**对比:VafastError 有统一的 `type` + `status` + `expose` 契约。**
|
|
145
|
+
|
|
146
|
+
### 组合优于约定 — 显式优于隐式
|
|
147
|
+
|
|
148
|
+
**Hono 完整示例:**
|
|
149
|
+
```typescript
|
|
150
|
+
import { Hono } from 'hono';
|
|
151
|
+
import { cors } from 'hono/cors';
|
|
152
|
+
|
|
153
|
+
const app = new Hono();
|
|
154
|
+
|
|
155
|
+
// 中间件作用域通过路径匹配,容易出错
|
|
156
|
+
app.use('/*', cors()); // 全局
|
|
157
|
+
app.use('/api/*', authMiddleware); // /api/* 但 /api 本身呢?
|
|
158
|
+
|
|
159
|
+
app.get('/public', (c) => c.text('public'));
|
|
160
|
+
app.get('/api/users', (c) => c.text('users'));
|
|
161
|
+
|
|
162
|
+
export default app;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Vafast 完整示例:**
|
|
166
|
+
```typescript
|
|
167
|
+
import { Server, createHandler } from 'vafast';
|
|
168
|
+
import type { Route, Middleware } from 'vafast';
|
|
169
|
+
|
|
170
|
+
const authMiddleware: Middleware = async (req, next) => {
|
|
171
|
+
const token = req.headers.get('Authorization');
|
|
172
|
+
if (!token) return new Response('Unauthorized', { status: 401 });
|
|
173
|
+
return next();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const routes: Route[] = [
|
|
177
|
+
// 无中间件
|
|
178
|
+
{ method: 'GET', path: '/public', handler: createHandler(() => 'public') },
|
|
179
|
+
// 仅 auth
|
|
180
|
+
{ method: 'GET', path: '/api/users', middleware: [authMiddleware], handler: createHandler(() => 'users') },
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
const server = new Server(routes);
|
|
184
|
+
export default { fetch: server.fetch };
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**对比:Vafast 的中间件直接声明在路由上,一目了然。**
|
|
188
|
+
|
|
189
|
+
### 类型注入 — 跨文件不丢失
|
|
190
|
+
|
|
191
|
+
**Hono 跨文件类型问题:**
|
|
192
|
+
```typescript
|
|
193
|
+
// -------- file: app.ts --------
|
|
194
|
+
import { Hono } from 'hono';
|
|
195
|
+
|
|
196
|
+
type Env = { Variables: { user: { id: string; role: string } } };
|
|
197
|
+
const app = new Hono<Env>();
|
|
198
|
+
|
|
199
|
+
// -------- file: routes.ts --------
|
|
200
|
+
import { Hono } from 'hono';
|
|
201
|
+
|
|
202
|
+
// 类型参数丢失!
|
|
203
|
+
export function setupRoutes(app: Hono) {
|
|
204
|
+
app.get('/profile', (c) => {
|
|
205
|
+
const user = c.get('user'); // ❌ 类型是 unknown
|
|
206
|
+
return c.json(user);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Vafast 跨文件类型完整:**
|
|
212
|
+
```typescript
|
|
213
|
+
// -------- file: types.ts --------
|
|
214
|
+
export type AuthContext = { user: { id: string; role: string } };
|
|
215
|
+
|
|
216
|
+
// -------- file: middleware/auth.ts --------
|
|
217
|
+
import type { Middleware } from 'vafast';
|
|
218
|
+
|
|
219
|
+
export const authMiddleware: Middleware = async (req, next) => {
|
|
220
|
+
const user = await verifyToken(req.headers.get('Authorization'));
|
|
221
|
+
(req as any).__locals = { user };
|
|
222
|
+
return next();
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// -------- file: handlers/profile.ts --------
|
|
226
|
+
import { createHandlerWithExtra } from 'vafast';
|
|
227
|
+
import type { AuthContext } from '../types';
|
|
228
|
+
|
|
229
|
+
// 类型在 Handler 级别定义,任意文件都能用!
|
|
230
|
+
export const getProfile = createHandlerWithExtra<AuthContext>((ctx) => {
|
|
231
|
+
const user = ctx.user; // ✅ 类型完整: { id: string; role: string }
|
|
232
|
+
return { profile: user, isAdmin: user.role === 'admin' };
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// -------- file: routes.ts --------
|
|
236
|
+
import { Server } from 'vafast';
|
|
237
|
+
import type { Route } from 'vafast';
|
|
238
|
+
import { authMiddleware } from './middleware/auth';
|
|
239
|
+
import { getProfile } from './handlers/profile';
|
|
240
|
+
|
|
241
|
+
const routes: Route[] = [
|
|
242
|
+
{ method: 'GET', path: '/profile', middleware: [authMiddleware], handler: getProfile },
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
const server = new Server(routes);
|
|
246
|
+
export default { fetch: server.fetch };
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**对比:Vafast 的类型跟着 Handler 走,而不是跟着 App 实例走。**
|
|
250
|
+
|
|
251
|
+
### 边缘原生 — 一行代码,任意运行时
|
|
252
|
+
|
|
253
|
+
**Bun 环境完整示例:**
|
|
254
|
+
```typescript
|
|
255
|
+
import { Server, createHandler } from 'vafast';
|
|
256
|
+
|
|
257
|
+
const server = new Server([
|
|
258
|
+
{ method: 'GET', path: '/', handler: createHandler(() => 'Hello Bun!') }
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
export default { port: 3000, fetch: server.fetch };
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Cloudflare Workers 完整示例:**
|
|
265
|
+
```typescript
|
|
266
|
+
import { Server, createHandler } from 'vafast';
|
|
267
|
+
|
|
268
|
+
const server = new Server([
|
|
269
|
+
{ method: 'GET', path: '/', handler: createHandler(() => 'Hello Workers!') }
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
export default { fetch: server.fetch };
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Node.js 完整示例:**
|
|
276
|
+
```typescript
|
|
277
|
+
import { Server, createHandler } from 'vafast';
|
|
278
|
+
import { serve } from '@vafast/node-server';
|
|
279
|
+
|
|
280
|
+
const server = new Server([
|
|
281
|
+
{ method: 'GET', path: '/', handler: createHandler(() => 'Hello Node!') }
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
serve({ fetch: server.fetch, port: 3000 }, () => {
|
|
285
|
+
console.log('Server running on http://localhost:3000');
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**对比:同一套代码,只需改导出方式即可切换运行时。**
|
|
290
|
+
|
|
291
|
+
### 零样板 — 一个文件,即刻运行
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# ❌ NestJS - 需要脚手架和大量文件
|
|
295
|
+
nest new my-app # 生成 20+ 文件
|
|
296
|
+
|
|
297
|
+
# ❌ Express - 需要配置和样板代码
|
|
298
|
+
npm init && npm install express && mkdir routes controllers...
|
|
299
|
+
|
|
300
|
+
# ✅ Vafast - 一个文件搞定
|
|
301
|
+
echo "import { Server } from 'vafast';
|
|
302
|
+
const server = new Server([{ method: 'GET', path: '/', handler: () => 'Hi' }]);
|
|
303
|
+
export default { fetch: server.fetch };" > index.ts && bun index.ts
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### 与 Elysia/Hono 详细对比
|
|
307
|
+
|
|
308
|
+
| 特性 | Elysia | Hono | **Vafast** |
|
|
309
|
+
|------|--------|------|------------|
|
|
310
|
+
| **路由风格** | 链式 builder | 链式 builder | **声明式数组** |
|
|
311
|
+
| **路由一览性** | 分散在链中 | 分散在链中 | **一个数组看全部** |
|
|
312
|
+
| **中间件绑定** | .use() 隐式 | .use() 路径匹配 | **显式声明在路由上** |
|
|
313
|
+
| **错误类型** | error() 函数 | HTTPException | **VafastError 契约** |
|
|
314
|
+
| **类型推断** | 优秀 | 良好 | **优秀 (TypeBox)** |
|
|
315
|
+
| **跨文件类型** | ⚠️ 链断裂丢失 | ❌ 实例绑定丢失 | **✅ Handler 级独立** |
|
|
316
|
+
| **类型定义位置** | 链式调用上下文 | App 实例泛型 | **Handler 泛型参数** |
|
|
317
|
+
| **性能 (RPS)** | ~118K | ~56K | **~101K** |
|
|
318
|
+
| **学习曲线** | 中等 | 简单 | **简单** |
|
|
319
|
+
| **API 风格** | 函数式链 | Express-like | **配置式** |
|
|
320
|
+
|
|
321
|
+
### 为什么选择 Vafast?
|
|
322
|
+
|
|
323
|
+
| 如果你... | 选择 |
|
|
324
|
+
|----------|------|
|
|
325
|
+
| 追求极致性能 | Elysia (~118K) > **Vafast (~101K)** > Hono (~56K) |
|
|
326
|
+
| 喜欢链式 API | Elysia 或 Hono |
|
|
327
|
+
| **需要路由一览表** | **✅ Vafast** |
|
|
328
|
+
| **需要精确中间件控制** | **✅ Vafast** |
|
|
329
|
+
| **需要结构化错误** | **✅ Vafast** |
|
|
330
|
+
| **大型项目多文件拆分** | **✅ Vafast (类型不丢失)** |
|
|
331
|
+
| **团队协作类型安全** | **✅ Vafast** |
|
|
332
|
+
| 从 Express 迁移 | Hono (API 相似) |
|
|
333
|
+
|
|
47
334
|
## 🎯 核心功能
|
|
48
335
|
|
|
49
336
|
- ⚡ **JIT 编译验证器** - Schema 验证器编译缓存,避免重复编译
|
|
@@ -194,10 +481,18 @@ serve({ fetch: server.fetch, port: 3000 });
|
|
|
194
481
|
|
|
195
482
|
## 📚 文档
|
|
196
483
|
|
|
484
|
+
### 入门
|
|
197
485
|
- [快速开始](./docs/getting-started/quickstart.md)
|
|
198
|
-
- [API 参考](./docs/api/)
|
|
199
486
|
- [示例代码](./examples/)
|
|
200
487
|
|
|
488
|
+
### 架构设计
|
|
489
|
+
- [路由设计与网关架构](./docs/router-design.md) - 声明式路由的设计哲学、AI 时代能力、网关优势
|
|
490
|
+
- [本地工具模式](./docs/local-tools-mode.md) - 声明式路由作为 AI Tools,无需 HTTP 服务
|
|
491
|
+
|
|
492
|
+
### 参考
|
|
493
|
+
- [服务器优化](./docs/server-optimization.md)
|
|
494
|
+
- [认证系统](./docs/auth.md)
|
|
495
|
+
|
|
201
496
|
## 🤝 贡献
|
|
202
497
|
|
|
203
498
|
欢迎贡献!请查看 [贡献指南](./CONTRIBUTING.md)。
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,6 @@ export * from "./auth/token";
|
|
|
9
9
|
export * from "./middleware/auth";
|
|
10
10
|
export * from "./defineRoute";
|
|
11
11
|
export * from "./types/index";
|
|
12
|
+
export { serve } from "./serve";
|
|
13
|
+
export type { ServeOptions, ServeResult, FetchHandler } from "./serve";
|
|
12
14
|
export { Type } from "@sinclair/typebox";
|
package/dist/index.js
CHANGED
|
@@ -9,5 +9,7 @@ export * from "./auth/token";
|
|
|
9
9
|
export * from "./middleware/auth";
|
|
10
10
|
export * from "./defineRoute";
|
|
11
11
|
export * from "./types/index";
|
|
12
|
+
// 统一的 serve 函数(自动适配 Bun/Node.js)
|
|
13
|
+
export { serve } from "./serve";
|
|
12
14
|
// 重新导出 Type 以便用户使用
|
|
13
15
|
export { Type } from "@sinclair/typebox";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vafast/node-server
|
|
3
|
+
* Node.js 适配器 - 为 Vafast 提供高性能 Node.js 运行时支持
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { serve } from "@vafast/node-server";
|
|
8
|
+
* import { Server } from "vafast";
|
|
9
|
+
*
|
|
10
|
+
* const app = new Server([
|
|
11
|
+
* { method: "GET", path: "/", handler: () => "Hello World" },
|
|
12
|
+
* ]);
|
|
13
|
+
*
|
|
14
|
+
* serve({ fetch: app.fetch, port: 3000 }, () => {
|
|
15
|
+
* console.log("Server running on http://localhost:3000");
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export { serve, createAdaptorServer } from "./serve";
|
|
20
|
+
export type { ServeOptions, ServeResult, FetchHandler } from "./serve";
|
|
21
|
+
export { createProxyRequest } from "./request";
|
|
22
|
+
export { writeResponse, writeResponseSimple } from "./response";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vafast/node-server
|
|
3
|
+
* Node.js 适配器 - 为 Vafast 提供高性能 Node.js 运行时支持
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { serve } from "@vafast/node-server";
|
|
8
|
+
* import { Server } from "vafast";
|
|
9
|
+
*
|
|
10
|
+
* const app = new Server([
|
|
11
|
+
* { method: "GET", path: "/", handler: () => "Hello World" },
|
|
12
|
+
* ]);
|
|
13
|
+
*
|
|
14
|
+
* serve({ fetch: app.fetch, port: 3000 }, () => {
|
|
15
|
+
* console.log("Server running on http://localhost:3000");
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export { serve, createAdaptorServer } from "./serve";
|
|
20
|
+
export { createProxyRequest } from "./request";
|
|
21
|
+
export { writeResponse, writeResponseSimple } from "./response";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 优化的 Request 代理
|
|
3
|
+
* 延迟创建真实 Request,减少不必要的对象分配
|
|
4
|
+
*/
|
|
5
|
+
import type { IncomingMessage } from "node:http";
|
|
6
|
+
/**
|
|
7
|
+
* 创建代理 Request
|
|
8
|
+
* @param incoming Node.js IncomingMessage
|
|
9
|
+
* @param defaultHost 默认主机名
|
|
10
|
+
*/
|
|
11
|
+
export declare function createProxyRequest(incoming: IncomingMessage, defaultHost: string): Request;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 优化的 Request 代理
|
|
3
|
+
* 延迟创建真实 Request,减少不必要的对象分配
|
|
4
|
+
*/
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
// 内部 Symbol
|
|
7
|
+
const requestCache = Symbol("requestCache");
|
|
8
|
+
const incomingKey = Symbol("incoming");
|
|
9
|
+
const urlKey = Symbol("url");
|
|
10
|
+
const headersKey = Symbol("headers");
|
|
11
|
+
/**
|
|
12
|
+
* 从 rawHeaders 高效解析 Headers
|
|
13
|
+
*/
|
|
14
|
+
function parseHeaders(rawHeaders) {
|
|
15
|
+
const headers = new Headers();
|
|
16
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
17
|
+
const key = rawHeaders[i];
|
|
18
|
+
const value = rawHeaders[i + 1];
|
|
19
|
+
// 跳过 HTTP/2 伪头 (以 : 开头)
|
|
20
|
+
if (key.charCodeAt(0) !== 58) {
|
|
21
|
+
headers.append(key, value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return headers;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 将 Node.js ReadableStream 转换为 Web 标准 ReadableStream
|
|
28
|
+
* Node.js 和 Web 标准的 ReadableStream 在运行时兼容,但 TypeScript 类型不同
|
|
29
|
+
*/
|
|
30
|
+
function toWebStream(nodeStream) {
|
|
31
|
+
// Node.js ReadableStream 和 Web ReadableStream 在运行时是兼容的
|
|
32
|
+
// 这里使用类型断言是安全的,因为 Node.js >= 18 的 stream/web 完全实现了 WHATWG Streams 标准
|
|
33
|
+
return nodeStream;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 创建真实的 Request 对象
|
|
37
|
+
*/
|
|
38
|
+
function createRealRequest(proxy) {
|
|
39
|
+
const incoming = proxy[incomingKey];
|
|
40
|
+
const method = incoming.method || "GET";
|
|
41
|
+
const init = {
|
|
42
|
+
method,
|
|
43
|
+
headers: proxy[headersKey] || parseHeaders(incoming.rawHeaders),
|
|
44
|
+
};
|
|
45
|
+
// 只有非 GET/HEAD 请求才有 body
|
|
46
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
47
|
+
// 使用 Node.js 原生流转换,避免收集 chunks
|
|
48
|
+
const nodeWebStream = Readable.toWeb(incoming);
|
|
49
|
+
init.body = toWebStream(nodeWebStream);
|
|
50
|
+
init.duplex = "half";
|
|
51
|
+
}
|
|
52
|
+
return new Request(proxy[urlKey], init);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Request 代理原型
|
|
56
|
+
* 使用 Object.defineProperty 定义属性以支持 this 绑定
|
|
57
|
+
*/
|
|
58
|
+
const requestPrototype = {};
|
|
59
|
+
// 定义 method 属性
|
|
60
|
+
Object.defineProperty(requestPrototype, "method", {
|
|
61
|
+
get() {
|
|
62
|
+
const self = this;
|
|
63
|
+
return self[incomingKey].method || "GET";
|
|
64
|
+
},
|
|
65
|
+
enumerable: true,
|
|
66
|
+
});
|
|
67
|
+
// 定义 url 属性
|
|
68
|
+
Object.defineProperty(requestPrototype, "url", {
|
|
69
|
+
get() {
|
|
70
|
+
const self = this;
|
|
71
|
+
return self[urlKey];
|
|
72
|
+
},
|
|
73
|
+
enumerable: true,
|
|
74
|
+
});
|
|
75
|
+
// 定义 headers 属性(延迟解析)
|
|
76
|
+
Object.defineProperty(requestPrototype, "headers", {
|
|
77
|
+
get() {
|
|
78
|
+
const self = this;
|
|
79
|
+
if (!self[headersKey]) {
|
|
80
|
+
self[headersKey] = parseHeaders(self[incomingKey].rawHeaders);
|
|
81
|
+
}
|
|
82
|
+
return self[headersKey];
|
|
83
|
+
},
|
|
84
|
+
enumerable: true,
|
|
85
|
+
});
|
|
86
|
+
// 定义 _getRequest 方法(获取或创建真实 Request)
|
|
87
|
+
Object.defineProperty(requestPrototype, "_getRequest", {
|
|
88
|
+
value: function () {
|
|
89
|
+
const self = this;
|
|
90
|
+
if (!self[requestCache]) {
|
|
91
|
+
self[requestCache] = createRealRequest(self);
|
|
92
|
+
}
|
|
93
|
+
return self[requestCache];
|
|
94
|
+
},
|
|
95
|
+
enumerable: false,
|
|
96
|
+
});
|
|
97
|
+
// 代理需要访问真实 Request 的属性
|
|
98
|
+
const proxyGetters = [
|
|
99
|
+
"body",
|
|
100
|
+
"bodyUsed",
|
|
101
|
+
"signal",
|
|
102
|
+
"cache",
|
|
103
|
+
"credentials",
|
|
104
|
+
"destination",
|
|
105
|
+
"integrity",
|
|
106
|
+
"mode",
|
|
107
|
+
"redirect",
|
|
108
|
+
"referrer",
|
|
109
|
+
"referrerPolicy",
|
|
110
|
+
"keepalive",
|
|
111
|
+
];
|
|
112
|
+
proxyGetters.forEach((key) => {
|
|
113
|
+
Object.defineProperty(requestPrototype, key, {
|
|
114
|
+
get() {
|
|
115
|
+
const self = this;
|
|
116
|
+
return self._getRequest()[key];
|
|
117
|
+
},
|
|
118
|
+
enumerable: true,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// 代理需要调用真实 Request 的方法
|
|
122
|
+
const proxyMethods = [
|
|
123
|
+
"arrayBuffer",
|
|
124
|
+
"blob",
|
|
125
|
+
"clone",
|
|
126
|
+
"formData",
|
|
127
|
+
"json",
|
|
128
|
+
"text",
|
|
129
|
+
];
|
|
130
|
+
proxyMethods.forEach((key) => {
|
|
131
|
+
Object.defineProperty(requestPrototype, key, {
|
|
132
|
+
value: function () {
|
|
133
|
+
const self = this;
|
|
134
|
+
const req = self._getRequest();
|
|
135
|
+
return req[key].call(req);
|
|
136
|
+
},
|
|
137
|
+
enumerable: true,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
// 设置原型链
|
|
141
|
+
Object.setPrototypeOf(requestPrototype, Request.prototype);
|
|
142
|
+
/**
|
|
143
|
+
* 创建代理 Request
|
|
144
|
+
* @param incoming Node.js IncomingMessage
|
|
145
|
+
* @param defaultHost 默认主机名
|
|
146
|
+
*/
|
|
147
|
+
export function createProxyRequest(incoming, defaultHost) {
|
|
148
|
+
const req = Object.create(requestPrototype);
|
|
149
|
+
req[incomingKey] = incoming;
|
|
150
|
+
// 构建 URL
|
|
151
|
+
const host = incoming.headers.host || defaultHost;
|
|
152
|
+
const protocol = incoming.socket.encrypted
|
|
153
|
+
? "https"
|
|
154
|
+
: "http";
|
|
155
|
+
req[urlKey] = `${protocol}://${host}${incoming.url || "/"}`;
|
|
156
|
+
return req;
|
|
157
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 优化的 Response 写入
|
|
3
|
+
* 流式写入,避免内存拷贝
|
|
4
|
+
*/
|
|
5
|
+
import type { ServerResponse } from "node:http";
|
|
6
|
+
/**
|
|
7
|
+
* 将 Web Response 写入 Node.js ServerResponse
|
|
8
|
+
* 流式写入,零拷贝
|
|
9
|
+
*/
|
|
10
|
+
export declare function writeResponse(response: Response, outgoing: ServerResponse): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* 简化版写入(用于已知小体积响应)
|
|
13
|
+
* 直接 arrayBuffer 转换,适用于确定的小响应
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeResponseSimple(response: Response, outgoing: ServerResponse): Promise<void>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 优化的 Response 写入
|
|
3
|
+
* 流式写入,避免内存拷贝
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 构建 Node.js 响应头
|
|
7
|
+
* 处理 set-cookie 多值情况
|
|
8
|
+
*/
|
|
9
|
+
function buildOutgoingHeaders(headers) {
|
|
10
|
+
const result = {};
|
|
11
|
+
const cookies = [];
|
|
12
|
+
headers.forEach((value, key) => {
|
|
13
|
+
if (key === "set-cookie") {
|
|
14
|
+
cookies.push(value);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
result[key] = value;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
if (cookies.length > 0) {
|
|
21
|
+
result["set-cookie"] = cookies;
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 流式写入 Response body 到 ServerResponse
|
|
27
|
+
* 支持背压处理,避免内存溢出
|
|
28
|
+
*/
|
|
29
|
+
async function writeBodyStream(body, outgoing) {
|
|
30
|
+
const reader = body.getReader();
|
|
31
|
+
try {
|
|
32
|
+
while (true) {
|
|
33
|
+
const { done, value } = await reader.read();
|
|
34
|
+
if (done) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
// 背压处理:如果写入返回 false,等待 drain 事件
|
|
38
|
+
const canContinue = outgoing.write(value);
|
|
39
|
+
if (!canContinue) {
|
|
40
|
+
await new Promise((resolve) => {
|
|
41
|
+
outgoing.once("drain", resolve);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
reader.releaseLock();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 将 Web Response 写入 Node.js ServerResponse
|
|
52
|
+
* 流式写入,零拷贝
|
|
53
|
+
*/
|
|
54
|
+
export async function writeResponse(response, outgoing) {
|
|
55
|
+
// 设置状态码
|
|
56
|
+
outgoing.statusCode = response.status;
|
|
57
|
+
// 设置响应头
|
|
58
|
+
const headers = buildOutgoingHeaders(response.headers);
|
|
59
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
60
|
+
outgoing.setHeader(key, value);
|
|
61
|
+
}
|
|
62
|
+
const body = response.body;
|
|
63
|
+
// 无 body 的情况
|
|
64
|
+
if (!body) {
|
|
65
|
+
outgoing.end();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// 流式写入 body
|
|
69
|
+
try {
|
|
70
|
+
await writeBodyStream(body, outgoing);
|
|
71
|
+
outgoing.end();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// 处理客户端提前断开等情况
|
|
75
|
+
if (!outgoing.destroyed) {
|
|
76
|
+
outgoing.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 简化版写入(用于已知小体积响应)
|
|
82
|
+
* 直接 arrayBuffer 转换,适用于确定的小响应
|
|
83
|
+
*/
|
|
84
|
+
export async function writeResponseSimple(response, outgoing) {
|
|
85
|
+
outgoing.statusCode = response.status;
|
|
86
|
+
const headers = buildOutgoingHeaders(response.headers);
|
|
87
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
88
|
+
outgoing.setHeader(key, value);
|
|
89
|
+
}
|
|
90
|
+
const body = response.body;
|
|
91
|
+
if (!body) {
|
|
92
|
+
outgoing.end();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// 对于小响应,直接读取全部内容
|
|
96
|
+
const buffer = await response.arrayBuffer();
|
|
97
|
+
outgoing.end(Buffer.from(buffer));
|
|
98
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js 服务器适配器
|
|
3
|
+
* 提供类似 Bun.serve 的 API
|
|
4
|
+
*/
|
|
5
|
+
import { type Server as HttpServer } from "node:http";
|
|
6
|
+
/** fetch 函数类型 */
|
|
7
|
+
export type FetchHandler = (request: Request) => Response | Promise<Response>;
|
|
8
|
+
/** serve 配置选项 */
|
|
9
|
+
export interface ServeOptions {
|
|
10
|
+
/** fetch 处理函数 */
|
|
11
|
+
fetch: FetchHandler;
|
|
12
|
+
/** 端口号,默认 3000 */
|
|
13
|
+
port?: number;
|
|
14
|
+
/** 主机名,默认 0.0.0.0 */
|
|
15
|
+
hostname?: string;
|
|
16
|
+
/** 错误处理函数 */
|
|
17
|
+
onError?: (error: Error) => Response | Promise<Response>;
|
|
18
|
+
}
|
|
19
|
+
/** serve 返回的服务器信息 */
|
|
20
|
+
export interface ServeResult {
|
|
21
|
+
/** Node.js HTTP Server 实例 */
|
|
22
|
+
server: HttpServer;
|
|
23
|
+
/** 服务器端口 */
|
|
24
|
+
port: number;
|
|
25
|
+
/** 服务器主机名 */
|
|
26
|
+
hostname: string;
|
|
27
|
+
/** 关闭服务器 */
|
|
28
|
+
stop: () => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 启动 HTTP 服务器
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { serve } from "@vafast/node-server";
|
|
36
|
+
* import { Server } from "vafast";
|
|
37
|
+
*
|
|
38
|
+
* const app = new Server([
|
|
39
|
+
* { method: "GET", path: "/", handler: () => "Hello World" },
|
|
40
|
+
* ]);
|
|
41
|
+
*
|
|
42
|
+
* serve({ fetch: app.fetch, port: 3000 }, () => {
|
|
43
|
+
* console.log("Server running on http://localhost:3000");
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function serve(options: ServeOptions, callback?: () => void): ServeResult;
|
|
48
|
+
/**
|
|
49
|
+
* 创建适配器服务器(不自动启动)
|
|
50
|
+
* 用于需要更多控制的场景
|
|
51
|
+
*/
|
|
52
|
+
export declare function createAdaptorServer(fetch: FetchHandler, onError?: (error: Error) => Response | Promise<Response>): HttpServer;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js 服务器适配器
|
|
3
|
+
* 提供类似 Bun.serve 的 API
|
|
4
|
+
*/
|
|
5
|
+
import { createServer, } from "node:http";
|
|
6
|
+
import { createProxyRequest } from "./request";
|
|
7
|
+
import { writeResponse } from "./response";
|
|
8
|
+
/**
|
|
9
|
+
* 创建请求处理函数
|
|
10
|
+
*/
|
|
11
|
+
function createRequestHandler(fetch, defaultHost, onError) {
|
|
12
|
+
return async (incoming, outgoing) => {
|
|
13
|
+
try {
|
|
14
|
+
// 创建代理 Request(延迟创建真实 Request)
|
|
15
|
+
const request = createProxyRequest(incoming, defaultHost);
|
|
16
|
+
// 调用 fetch handler
|
|
17
|
+
const response = await fetch(request);
|
|
18
|
+
// 流式写入 Response
|
|
19
|
+
await writeResponse(response, outgoing);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// 错误处理
|
|
23
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
24
|
+
if (onError) {
|
|
25
|
+
try {
|
|
26
|
+
const errorResponse = await onError(err);
|
|
27
|
+
await writeResponse(errorResponse, outgoing);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// onError 也失败了,返回 500
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// 默认错误响应
|
|
35
|
+
if (!outgoing.headersSent) {
|
|
36
|
+
outgoing.statusCode = 500;
|
|
37
|
+
outgoing.setHeader("Content-Type", "text/plain");
|
|
38
|
+
outgoing.end("Internal Server Error");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 启动 HTTP 服务器
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { serve } from "@vafast/node-server";
|
|
49
|
+
* import { Server } from "vafast";
|
|
50
|
+
*
|
|
51
|
+
* const app = new Server([
|
|
52
|
+
* { method: "GET", path: "/", handler: () => "Hello World" },
|
|
53
|
+
* ]);
|
|
54
|
+
*
|
|
55
|
+
* serve({ fetch: app.fetch, port: 3000 }, () => {
|
|
56
|
+
* console.log("Server running on http://localhost:3000");
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function serve(options, callback) {
|
|
61
|
+
const { fetch, port = 3000, hostname = "0.0.0.0", onError } = options;
|
|
62
|
+
const defaultHost = `${hostname === "0.0.0.0" ? "localhost" : hostname}:${port}`;
|
|
63
|
+
const handler = createRequestHandler(fetch, defaultHost, onError);
|
|
64
|
+
const server = createServer(handler);
|
|
65
|
+
// 启动服务器
|
|
66
|
+
server.listen(port, hostname, callback);
|
|
67
|
+
return {
|
|
68
|
+
server,
|
|
69
|
+
port,
|
|
70
|
+
hostname,
|
|
71
|
+
stop: () => new Promise((resolve, reject) => {
|
|
72
|
+
server.close((err) => {
|
|
73
|
+
if (err)
|
|
74
|
+
reject(err);
|
|
75
|
+
else
|
|
76
|
+
resolve();
|
|
77
|
+
});
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 创建适配器服务器(不自动启动)
|
|
83
|
+
* 用于需要更多控制的场景
|
|
84
|
+
*/
|
|
85
|
+
export function createAdaptorServer(fetch, onError) {
|
|
86
|
+
const handler = createRequestHandler(fetch, "localhost", onError);
|
|
87
|
+
return createServer(handler);
|
|
88
|
+
}
|
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一的 serve 函数
|
|
3
|
+
* 使用 Node.js 原生 http 模块,兼容 Bun 和 Node.js
|
|
4
|
+
*
|
|
5
|
+
* 基准测试结果(Bun 环境):
|
|
6
|
+
* - Bun.serve: 35,422 req/s
|
|
7
|
+
* - node:http: 38,075 req/s
|
|
8
|
+
*
|
|
9
|
+
* node:http 在 Bun 下性能甚至更好,无需使用 Bun API
|
|
10
|
+
*/
|
|
11
|
+
export { serve, createAdaptorServer } from "./node-server/serve";
|
|
12
|
+
export type { ServeOptions, ServeResult, FetchHandler, } from "./node-server/serve";
|
package/dist/serve.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一的 serve 函数
|
|
3
|
+
* 使用 Node.js 原生 http 模块,兼容 Bun 和 Node.js
|
|
4
|
+
*
|
|
5
|
+
* 基准测试结果(Bun 环境):
|
|
6
|
+
* - Bun.serve: 35,422 req/s
|
|
7
|
+
* - node:http: 38,075 req/s
|
|
8
|
+
*
|
|
9
|
+
* node:http 在 Bun 下性能甚至更好,无需使用 Bun API
|
|
10
|
+
*/
|
|
11
|
+
export { serve, createAdaptorServer } from "./node-server/serve";
|
package/dist/server/server.js
CHANGED
|
@@ -82,7 +82,8 @@ export class Server extends BaseServer {
|
|
|
82
82
|
if (match) {
|
|
83
83
|
req.params = match.params;
|
|
84
84
|
// 优先使用预编译的处理链(仅当全局中间件未变化时)
|
|
85
|
-
if (match.compiled &&
|
|
85
|
+
if (match.compiled &&
|
|
86
|
+
this.globalMiddleware.length === this.compiledWithMiddlewareCount) {
|
|
86
87
|
return match.compiled(req);
|
|
87
88
|
}
|
|
88
89
|
// 回退:运行时组合中间件
|
package/dist/utils/parsers.js
CHANGED
|
@@ -180,9 +180,10 @@ export function parseCookiesFast(req) {
|
|
|
180
180
|
const key = trimmed.substring(0, eqIndex).trim();
|
|
181
181
|
const value = trimmed.substring(eqIndex + 1).trim();
|
|
182
182
|
// 移除引号
|
|
183
|
-
result[key] =
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
result[key] =
|
|
184
|
+
value.startsWith('"') && value.endsWith('"')
|
|
185
|
+
? value.slice(1, -1)
|
|
186
|
+
: value;
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
return result;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vafast",
|
|
3
|
-
"version": "0.2
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "极简结构化Web框架,支持 Bun 和 Node.js。Go风格,函数优先。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
"require": "./dist/index.js",
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
25
|
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./node-server": {
|
|
28
|
+
"import": "./dist/node-server/index.js",
|
|
29
|
+
"require": "./dist/node-server/index.js",
|
|
30
|
+
"types": "./dist/node-server/index.d.ts",
|
|
31
|
+
"default": "./dist/node-server/index.js"
|
|
26
32
|
}
|
|
27
33
|
},
|
|
28
34
|
"types": "./dist/index.d.ts",
|
|
@@ -53,6 +59,7 @@
|
|
|
53
59
|
},
|
|
54
60
|
"keywords": [
|
|
55
61
|
"bun",
|
|
62
|
+
"node",
|
|
56
63
|
"framework",
|
|
57
64
|
"minimal",
|
|
58
65
|
"router",
|