vafast 0.5.5 → 0.5.7
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 +418 -163
- package/dist/{defineRoute-BNqVD0_t.d.mts → defineRoute-FhAN4ivP.d.mts} +20 -3
- package/dist/defineRoute.d.mts +2 -2
- package/dist/defineRoute.mjs +18 -6
- package/dist/defineRoute.mjs.map +1 -1
- package/dist/{index-DCJOWsRE.d.mts → index-XcXGKQqt.d.mts} +3 -3
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +4 -4
- package/dist/{middleware-BR-R4p0M.d.mts → middleware-CrD2gfzt.d.mts} +3 -3
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.mjs +1 -1
- package/dist/monitoring/index.d.mts +3 -3
- package/dist/monitoring/native-monitor.d.mts +3 -3
- package/dist/{response-BNLzz4Tq.d.mts → response-CJcO5s7Q.d.mts} +23 -13
- package/dist/{response-CQ1IgWei.mjs → response-dgNpkPIp.mjs} +27 -17
- package/dist/response-dgNpkPIp.mjs.map +1 -0
- package/dist/{route-registry-DZv4vAjT.d.mts → route-registry-YIco7opr.d.mts} +2 -2
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.mjs +3 -3
- package/dist/server/server-factory.d.mts +3 -3
- package/dist/server/server-factory.mjs +3 -3
- package/dist/server/server.d.mts +2 -2
- package/dist/server/server.mjs +2 -2
- package/dist/{server-Bicf_7Hx.d.mts → server-8X5Hgu7h.d.mts} +2 -2
- package/dist/{server-CZLmrJSk.mjs → server-AJWK-vUI.mjs} +2 -2
- package/dist/{server-CZLmrJSk.mjs.map → server-AJWK-vUI.mjs.map} +1 -1
- package/dist/{server-Bm0BGm01.mjs → server-BCjY3a63.mjs} +5 -6
- package/dist/server-BCjY3a63.mjs.map +1 -0
- package/dist/{sse-CWNz0ky7.mjs → sse-CAOZ-rXY.mjs} +2 -3
- package/dist/{sse-CWNz0ky7.mjs.map → sse-CAOZ-rXY.mjs.map} +1 -1
- package/dist/{sse-DYuFPif9.d.mts → sse-DyI21Jqk.d.mts} +2 -2
- package/dist/utils/index.d.mts +4 -4
- package/dist/utils/index.mjs +2 -2
- package/dist/utils/response.d.mts +1 -1
- package/dist/utils/response.mjs +1 -1
- package/dist/utils/route-registry.d.mts +2 -2
- package/dist/utils/sse.d.mts +2 -2
- package/dist/utils/sse.mjs +1 -1
- package/package.json +1 -1
- package/dist/response-CQ1IgWei.mjs.map +0 -1
- package/dist/server-Bm0BGm01.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -8,21 +8,24 @@
|
|
|
8
8
|
|
|
9
9
|
> Vafast 不只是框架,更是一种 **结构、清晰、可控** 的开发哲学。
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
import { Server, createHandler } from 'vafast';
|
|
13
|
-
|
|
14
|
-
const server = new Server([
|
|
15
|
-
{ method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') }
|
|
16
|
-
]);
|
|
11
|
+
## 🚀 快速开始
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
```bash
|
|
14
|
+
npx create-vafast-app
|
|
19
15
|
```
|
|
20
16
|
|
|
17
|
+
按照提示输入项目名称,然后运行:
|
|
18
|
+
|
|
21
19
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
cd my-vafast-app
|
|
21
|
+
npm install
|
|
22
|
+
npm run dev
|
|
24
23
|
```
|
|
25
24
|
|
|
25
|
+
访问 [http://localhost:3000](http://localhost:3000) 即可看到 "Hello Vafast!"。
|
|
26
|
+
|
|
27
|
+
> 💡 想要手动配置?查看 [安装](#-安装) 部分。
|
|
28
|
+
|
|
26
29
|
## ⚡ 性能
|
|
27
30
|
|
|
28
31
|
| 框架 | RPS | 相对性能 |
|
|
@@ -37,10 +40,47 @@ npx tsx index.ts
|
|
|
37
40
|
|
|
38
41
|
## 📦 安装
|
|
39
42
|
|
|
43
|
+
### 方式一:使用脚手架(推荐)
|
|
44
|
+
|
|
45
|
+
使用官方脚手架快速创建项目:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx create-vafast-app
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 方式二:手动安装
|
|
52
|
+
|
|
53
|
+
在现有项目中安装:
|
|
54
|
+
|
|
40
55
|
```bash
|
|
41
56
|
npm install vafast
|
|
42
57
|
```
|
|
43
58
|
|
|
59
|
+
然后创建 `index.ts`:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Server, defineRoute, defineRoutes, serve } from 'vafast';
|
|
63
|
+
|
|
64
|
+
const routes = defineRoutes([
|
|
65
|
+
defineRoute({
|
|
66
|
+
method: 'GET',
|
|
67
|
+
path: '/',
|
|
68
|
+
handler: () => 'Hello Vafast!'
|
|
69
|
+
})
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
const server = new Server(routes);
|
|
73
|
+
serve({ fetch: server.fetch, port: 3000 }, (info) => {
|
|
74
|
+
console.log(`🚀 Server running at http://localhost:${info.port}`);
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
运行:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx tsx index.ts
|
|
82
|
+
```
|
|
83
|
+
|
|
44
84
|
## 💡 设计哲学
|
|
45
85
|
|
|
46
86
|
### 结构即真相 — 无装饰器,无链式魔法
|
|
@@ -72,14 +112,25 @@ export default app;
|
|
|
72
112
|
|
|
73
113
|
**Vafast 完整示例:**
|
|
74
114
|
```typescript
|
|
75
|
-
import { Server,
|
|
76
|
-
import type { Route } from 'vafast';
|
|
115
|
+
import { Server, defineRoute, defineRoutes } from 'vafast';
|
|
77
116
|
|
|
78
|
-
const routes
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
117
|
+
const routes = defineRoutes([
|
|
118
|
+
defineRoute({
|
|
119
|
+
method: 'GET',
|
|
120
|
+
path: '/users',
|
|
121
|
+
handler: () => 'list users'
|
|
122
|
+
}),
|
|
123
|
+
defineRoute({
|
|
124
|
+
method: 'POST',
|
|
125
|
+
path: '/users',
|
|
126
|
+
handler: ({ body }) => body
|
|
127
|
+
}),
|
|
128
|
+
defineRoute({
|
|
129
|
+
method: 'GET',
|
|
130
|
+
path: '/users/:id',
|
|
131
|
+
handler: ({ params }) => `User ${params.id}`
|
|
132
|
+
}),
|
|
133
|
+
]);
|
|
83
134
|
|
|
84
135
|
const server = new Server(routes);
|
|
85
136
|
export default { fetch: server.fetch };
|
|
@@ -110,22 +161,21 @@ export default app;
|
|
|
110
161
|
|
|
111
162
|
**Vafast 完整示例:**
|
|
112
163
|
```typescript
|
|
113
|
-
import { Server,
|
|
114
|
-
import type { Route } from 'vafast';
|
|
164
|
+
import { Server, defineRoute, defineRoutes, err } from 'vafast';
|
|
115
165
|
|
|
116
|
-
const routes
|
|
117
|
-
{
|
|
166
|
+
const routes = defineRoutes([
|
|
167
|
+
defineRoute({
|
|
118
168
|
method: 'GET',
|
|
119
169
|
path: '/user',
|
|
120
|
-
handler:
|
|
121
|
-
const name =
|
|
170
|
+
handler: ({ query }) => {
|
|
171
|
+
const name = query.name;
|
|
122
172
|
if (!name) {
|
|
123
173
|
throw err.badRequest('Missing name'); // ✨ 简洁!
|
|
124
174
|
}
|
|
125
175
|
return `Hello, ${name}`;
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
];
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
]);
|
|
129
179
|
|
|
130
180
|
const server = new Server(routes);
|
|
131
181
|
export default { fetch: server.fetch };
|
|
@@ -155,21 +205,29 @@ export default app;
|
|
|
155
205
|
|
|
156
206
|
**Vafast 完整示例:**
|
|
157
207
|
```typescript
|
|
158
|
-
import { Server,
|
|
159
|
-
import type { Route, Middleware } from 'vafast';
|
|
208
|
+
import { Server, defineRoute, defineRoutes, defineMiddleware } from 'vafast';
|
|
160
209
|
|
|
161
|
-
const authMiddleware
|
|
210
|
+
const authMiddleware = defineMiddleware(async (req, next) => {
|
|
162
211
|
const token = req.headers.get('Authorization');
|
|
163
212
|
if (!token) return new Response('Unauthorized', { status: 401 });
|
|
164
213
|
return next();
|
|
165
|
-
};
|
|
214
|
+
});
|
|
166
215
|
|
|
167
|
-
const routes
|
|
216
|
+
const routes = defineRoutes([
|
|
168
217
|
// 无中间件
|
|
169
|
-
{
|
|
218
|
+
defineRoute({
|
|
219
|
+
method: 'GET',
|
|
220
|
+
path: '/public',
|
|
221
|
+
handler: () => 'public'
|
|
222
|
+
}),
|
|
170
223
|
// 仅 auth
|
|
171
|
-
{
|
|
172
|
-
|
|
224
|
+
defineRoute({
|
|
225
|
+
method: 'GET',
|
|
226
|
+
path: '/api/users',
|
|
227
|
+
middleware: [authMiddleware],
|
|
228
|
+
handler: () => 'users'
|
|
229
|
+
}),
|
|
230
|
+
]);
|
|
173
231
|
|
|
174
232
|
const server = new Server(routes);
|
|
175
233
|
export default { fetch: server.fetch };
|
|
@@ -177,6 +235,69 @@ export default { fetch: server.fetch };
|
|
|
177
235
|
|
|
178
236
|
**对比:Vafast 的中间件直接声明在路由上,一目了然。**
|
|
179
237
|
|
|
238
|
+
### 扩展字段 — 声明式元数据,赋能业务逻辑
|
|
239
|
+
|
|
240
|
+
**其他框架的问题:**
|
|
241
|
+
- 路由定义和元数据分离,难以统一管理
|
|
242
|
+
- 需要额外的配置文件或装饰器来存储 webhook、权限、计费等信息
|
|
243
|
+
- 元数据查询需要遍历路由或维护独立映射表
|
|
244
|
+
|
|
245
|
+
**Vafast 完整示例:**
|
|
246
|
+
```typescript
|
|
247
|
+
import { Server, defineRoute, defineRoutes, getRouteRegistry, defineMiddleware } from 'vafast';
|
|
248
|
+
|
|
249
|
+
// 计费中间件(基于路由元数据)
|
|
250
|
+
const billingMiddleware = defineMiddleware(async (req, next) => {
|
|
251
|
+
const route = getRouteRegistry().get(req.method, new URL(req.url).pathname);
|
|
252
|
+
|
|
253
|
+
// 从路由元数据读取计费配置
|
|
254
|
+
if (route?.billing) {
|
|
255
|
+
const { price, currency } = route.billing;
|
|
256
|
+
// 执行计费逻辑
|
|
257
|
+
await chargeUser(req, price, currency);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return next();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const routes = defineRoutes([
|
|
264
|
+
defineRoute({
|
|
265
|
+
method: 'POST',
|
|
266
|
+
path: '/ai/generate',
|
|
267
|
+
name: 'AI 生成',
|
|
268
|
+
description: '生成 AI 内容',
|
|
269
|
+
// ✨ 扩展字段:计费配置
|
|
270
|
+
billing: { price: 0.01, currency: 'USD' },
|
|
271
|
+
// ✨ 扩展字段:Webhook 事件
|
|
272
|
+
webhook: { eventKey: 'ai.generate', enabled: true },
|
|
273
|
+
// ✨ 扩展字段:权限要求
|
|
274
|
+
permission: 'ai.generate',
|
|
275
|
+
middleware: [billingMiddleware],
|
|
276
|
+
handler: async ({ body }) => {
|
|
277
|
+
const result = await generateAI(body.prompt);
|
|
278
|
+
return { result };
|
|
279
|
+
}
|
|
280
|
+
}),
|
|
281
|
+
defineRoute({
|
|
282
|
+
method: 'GET',
|
|
283
|
+
path: '/users',
|
|
284
|
+
// 免费 API,无需计费
|
|
285
|
+
handler: () => ({ users: [] })
|
|
286
|
+
}),
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
const server = new Server(routes);
|
|
290
|
+
|
|
291
|
+
// 查询所有需要计费的 API
|
|
292
|
+
const paidRoutes = getRouteRegistry().filter('billing');
|
|
293
|
+
// 查询所有 Webhook 事件
|
|
294
|
+
const webhookRoutes = getRouteRegistry().filter('webhook');
|
|
295
|
+
// 按权限筛选
|
|
296
|
+
const aiRoutes = getRouteRegistry().filterBy(r => r.permission?.startsWith('ai.'));
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**对比:Vafast 的扩展字段让路由定义成为单一数据源,元数据查询、中间件配置、业务逻辑都基于声明式配置。**
|
|
300
|
+
|
|
180
301
|
### 类型注入 — 跨文件不丢失
|
|
181
302
|
|
|
182
303
|
**Hono 跨文件类型问题:**
|
|
@@ -205,33 +326,37 @@ export function setupRoutes(app: Hono) {
|
|
|
205
326
|
export type AuthContext = { user: { id: string; role: string } };
|
|
206
327
|
|
|
207
328
|
// -------- file: middleware/auth.ts --------
|
|
208
|
-
import
|
|
329
|
+
import { defineMiddleware } from 'vafast';
|
|
330
|
+
import type { AuthContext } from '../types';
|
|
209
331
|
|
|
210
|
-
|
|
332
|
+
// 使用 defineMiddleware 定义带类型的中间件
|
|
333
|
+
export const authMiddleware = defineMiddleware<AuthContext>(async (req, next) => {
|
|
211
334
|
const user = await verifyToken(req.headers.get('Authorization'));
|
|
212
|
-
(
|
|
213
|
-
|
|
214
|
-
};
|
|
335
|
+
return next({ user }); // 通过 next 传递上下文
|
|
336
|
+
});
|
|
215
337
|
|
|
216
338
|
// -------- file: handlers/profile.ts --------
|
|
217
|
-
import {
|
|
218
|
-
import
|
|
339
|
+
import { defineRoute } from 'vafast';
|
|
340
|
+
import { authMiddleware } from '../middleware/auth';
|
|
219
341
|
|
|
220
|
-
//
|
|
221
|
-
export const
|
|
222
|
-
|
|
223
|
-
|
|
342
|
+
// 类型在路由级别定义,任意文件都能用!
|
|
343
|
+
export const getProfileRoute = defineRoute({
|
|
344
|
+
method: 'GET',
|
|
345
|
+
path: '/profile',
|
|
346
|
+
middleware: [authMiddleware],
|
|
347
|
+
handler: ({ user }) => {
|
|
348
|
+
// ✅ user 自动有类型: { id: string; role: string }
|
|
349
|
+
return { profile: user, isAdmin: user.role === 'admin' };
|
|
350
|
+
}
|
|
224
351
|
});
|
|
225
352
|
|
|
226
353
|
// -------- file: routes.ts --------
|
|
227
|
-
import { Server } from 'vafast';
|
|
228
|
-
import
|
|
229
|
-
import { authMiddleware } from './middleware/auth';
|
|
230
|
-
import { getProfile } from './handlers/profile';
|
|
354
|
+
import { Server, defineRoutes } from 'vafast';
|
|
355
|
+
import { getProfileRoute } from './handlers/profile';
|
|
231
356
|
|
|
232
|
-
const routes
|
|
233
|
-
|
|
234
|
-
];
|
|
357
|
+
const routes = defineRoutes([
|
|
358
|
+
getProfileRoute,
|
|
359
|
+
]);
|
|
235
360
|
|
|
236
361
|
const server = new Server(routes);
|
|
237
362
|
export default { fetch: server.fetch };
|
|
@@ -243,34 +368,49 @@ export default { fetch: server.fetch };
|
|
|
243
368
|
|
|
244
369
|
**Bun 环境完整示例:**
|
|
245
370
|
```typescript
|
|
246
|
-
import { Server,
|
|
371
|
+
import { Server, defineRoute, defineRoutes } from 'vafast';
|
|
247
372
|
|
|
248
|
-
const
|
|
249
|
-
{
|
|
373
|
+
const routes = defineRoutes([
|
|
374
|
+
defineRoute({
|
|
375
|
+
method: 'GET',
|
|
376
|
+
path: '/',
|
|
377
|
+
handler: () => 'Hello Bun!'
|
|
378
|
+
})
|
|
250
379
|
]);
|
|
251
380
|
|
|
381
|
+
const server = new Server(routes);
|
|
252
382
|
export default { port: 3000, fetch: server.fetch };
|
|
253
383
|
```
|
|
254
384
|
|
|
255
385
|
**Cloudflare Workers 完整示例:**
|
|
256
386
|
```typescript
|
|
257
|
-
import { Server,
|
|
387
|
+
import { Server, defineRoute, defineRoutes } from 'vafast';
|
|
258
388
|
|
|
259
|
-
const
|
|
260
|
-
{
|
|
389
|
+
const routes = defineRoutes([
|
|
390
|
+
defineRoute({
|
|
391
|
+
method: 'GET',
|
|
392
|
+
path: '/',
|
|
393
|
+
handler: () => 'Hello Workers!'
|
|
394
|
+
})
|
|
261
395
|
]);
|
|
262
396
|
|
|
397
|
+
const server = new Server(routes);
|
|
263
398
|
export default { fetch: server.fetch };
|
|
264
399
|
```
|
|
265
400
|
|
|
266
401
|
**Node.js 完整示例:**
|
|
267
402
|
```typescript
|
|
268
|
-
import { Server,
|
|
403
|
+
import { Server, defineRoute, defineRoutes, serve } from 'vafast';
|
|
269
404
|
|
|
270
|
-
const
|
|
271
|
-
{
|
|
405
|
+
const routes = defineRoutes([
|
|
406
|
+
defineRoute({
|
|
407
|
+
method: 'GET',
|
|
408
|
+
path: '/',
|
|
409
|
+
handler: () => 'Hello Node!'
|
|
410
|
+
})
|
|
272
411
|
]);
|
|
273
412
|
|
|
413
|
+
const server = new Server(routes);
|
|
274
414
|
serve({ fetch: server.fetch, port: 3000 }, () => {
|
|
275
415
|
console.log('Server running on http://localhost:3000');
|
|
276
416
|
});
|
|
@@ -287,9 +427,13 @@ nest new my-app # 生成 20+ 文件
|
|
|
287
427
|
# ❌ Express - 需要配置和样板代码
|
|
288
428
|
npm init && npm install express && mkdir routes controllers...
|
|
289
429
|
|
|
290
|
-
# ✅ Vafast -
|
|
291
|
-
|
|
292
|
-
|
|
430
|
+
# ✅ Vafast - 使用脚手架一键创建
|
|
431
|
+
npx create-vafast-app
|
|
432
|
+
|
|
433
|
+
# ✅ 或手动创建 - 一个文件搞定
|
|
434
|
+
echo "import { Server, defineRoute, defineRoutes } from 'vafast';
|
|
435
|
+
const routes = defineRoutes([defineRoute({ method: 'GET', path: '/', handler: () => 'Hi' })]);
|
|
436
|
+
const server = new Server(routes);
|
|
293
437
|
export default { fetch: server.fetch };" > index.ts && bun index.ts
|
|
294
438
|
```
|
|
295
439
|
|
|
@@ -304,6 +448,9 @@ export default { fetch: server.fetch };" > index.ts && bun index.ts
|
|
|
304
448
|
| **类型推断** | 优秀 | 良好 | **优秀 (TypeBox)** |
|
|
305
449
|
| **跨文件类型** | ⚠️ 链断裂丢失 | ❌ 实例绑定丢失 | **✅ Handler 级独立** |
|
|
306
450
|
| **类型定义位置** | 链式调用上下文 | App 实例泛型 | **Handler 泛型参数** |
|
|
451
|
+
| **扩展字段** | ❌ 不支持 | ❌ 不支持 | **✅ 任意扩展字段** |
|
|
452
|
+
| **元数据查询** | ❌ 需遍历 | ❌ 需遍历 | **✅ RouteRegistry API** |
|
|
453
|
+
| **业务集成** | ⚠️ 需额外配置 | ⚠️ 需额外配置 | **✅ 声明式集成** |
|
|
307
454
|
| **性能 (RPS)** | ~118K | ~56K | **~101K** |
|
|
308
455
|
| **学习曲线** | 中等 | 简单 | **简单** |
|
|
309
456
|
| **API 风格** | 函数式链 | Express-like | **配置式** |
|
|
@@ -317,8 +464,11 @@ export default { fetch: server.fetch };" > index.ts && bun index.ts
|
|
|
317
464
|
| **需要路由一览表** | **✅ Vafast** |
|
|
318
465
|
| **需要精确中间件控制** | **✅ Vafast** |
|
|
319
466
|
| **需要结构化错误** | **✅ Vafast** |
|
|
467
|
+
| **需要扩展字段(webhook、计费、权限)** | **✅ Vafast** |
|
|
468
|
+
| **需要元数据查询和筛选** | **✅ Vafast (RouteRegistry)** |
|
|
320
469
|
| **大型项目多文件拆分** | **✅ Vafast (类型不丢失)** |
|
|
321
470
|
| **团队协作类型安全** | **✅ Vafast** |
|
|
471
|
+
| **API 网关/微服务场景** | **✅ Vafast (声明式配置)** |
|
|
322
472
|
| 从 Express 迁移 | Hono (API 相似) |
|
|
323
473
|
|
|
324
474
|
## 🎯 核心功能
|
|
@@ -335,25 +485,31 @@ export default { fetch: server.fetch };" > index.ts && bun index.ts
|
|
|
335
485
|
Vafast 提供简洁、对称的响应 API:
|
|
336
486
|
|
|
337
487
|
```typescript
|
|
338
|
-
import {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
488
|
+
import { defineRoute, json, err } from 'vafast';
|
|
489
|
+
|
|
490
|
+
defineRoute({
|
|
491
|
+
method: 'POST',
|
|
492
|
+
path: '/users',
|
|
493
|
+
handler: ({ body }) => {
|
|
494
|
+
// ==================== 成功响应 ====================
|
|
495
|
+
return body // 200 + JSON(自动转换)
|
|
496
|
+
return json(body, 201) // 201 Created
|
|
497
|
+
return json(body, 200, { // 自定义头部
|
|
498
|
+
'X-Request-Id': 'abc123'
|
|
499
|
+
})
|
|
500
|
+
return 'Hello' // 200 + text/plain
|
|
501
|
+
return new Response(...) // 完全控制
|
|
502
|
+
|
|
503
|
+
// ==================== 错误响应 ====================
|
|
504
|
+
throw err.badRequest('参数错误') // 400
|
|
505
|
+
throw err.unauthorized('请先登录') // 401
|
|
506
|
+
throw err.forbidden('无权限') // 403
|
|
507
|
+
throw err.notFound('用户不存在') // 404
|
|
508
|
+
throw err.conflict('用户名已存在') // 409
|
|
509
|
+
throw err.internal('服务器错误') // 500
|
|
510
|
+
throw err('自定义错误', 422, 'CUSTOM_TYPE') // 自定义
|
|
511
|
+
}
|
|
345
512
|
})
|
|
346
|
-
return 'Hello' // 200 + text/plain
|
|
347
|
-
return new Response(...) // 完全控制
|
|
348
|
-
|
|
349
|
-
// ==================== 错误响应 ====================
|
|
350
|
-
throw err.badRequest('参数错误') // 400
|
|
351
|
-
throw err.unauthorized('请先登录') // 401
|
|
352
|
-
throw err.forbidden('无权限') // 403
|
|
353
|
-
throw err.notFound('用户不存在') // 404
|
|
354
|
-
throw err.conflict('用户名已存在') // 409
|
|
355
|
-
throw err.internal('服务器错误') // 500
|
|
356
|
-
throw err('自定义错误', 422, 'CUSTOM_TYPE') // 自定义
|
|
357
513
|
```
|
|
358
514
|
|
|
359
515
|
**API 速查表:**
|
|
@@ -372,20 +528,20 @@ throw err('自定义错误', 422, 'CUSTOM_TYPE') // 自定义
|
|
|
372
528
|
### 类型安全的路由
|
|
373
529
|
|
|
374
530
|
```typescript
|
|
375
|
-
import { Server,
|
|
531
|
+
import { Server, defineRoute, defineRoutes, Type } from 'vafast';
|
|
376
532
|
|
|
377
533
|
const routes = defineRoutes([
|
|
378
|
-
{
|
|
534
|
+
defineRoute({
|
|
379
535
|
method: 'POST',
|
|
380
536
|
path: '/users',
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
}
|
|
537
|
+
schema: {
|
|
538
|
+
body: Type.Object({ name: Type.String(), email: Type.String() })
|
|
539
|
+
},
|
|
540
|
+
handler: ({ body }) => {
|
|
541
|
+
// body.name 和 body.email 自动类型推断
|
|
542
|
+
return { success: true, user: body };
|
|
543
|
+
}
|
|
544
|
+
})
|
|
389
545
|
]);
|
|
390
546
|
|
|
391
547
|
const server = new Server(routes);
|
|
@@ -395,32 +551,34 @@ export default { port: 3000, fetch: server.fetch };
|
|
|
395
551
|
### 路径参数
|
|
396
552
|
|
|
397
553
|
```typescript
|
|
398
|
-
{
|
|
554
|
+
defineRoute({
|
|
399
555
|
method: 'GET',
|
|
400
556
|
path: '/users/:id',
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
)
|
|
405
|
-
}
|
|
557
|
+
schema: {
|
|
558
|
+
params: Type.Object({ id: Type.String() })
|
|
559
|
+
},
|
|
560
|
+
handler: ({ params }) => ({ userId: params.id })
|
|
561
|
+
})
|
|
406
562
|
```
|
|
407
563
|
|
|
408
564
|
### 中间件
|
|
409
565
|
|
|
410
566
|
```typescript
|
|
411
|
-
|
|
567
|
+
import { defineMiddleware } from 'vafast';
|
|
568
|
+
|
|
569
|
+
const authMiddleware = defineMiddleware(async (req, next) => {
|
|
412
570
|
const token = req.headers.get('Authorization');
|
|
413
571
|
if (!token) return new Response('Unauthorized', { status: 401 });
|
|
414
|
-
return next(
|
|
415
|
-
};
|
|
572
|
+
return next();
|
|
573
|
+
});
|
|
416
574
|
|
|
417
575
|
const routes = defineRoutes([
|
|
418
|
-
{
|
|
576
|
+
defineRoute({
|
|
419
577
|
method: 'GET',
|
|
420
578
|
path: '/protected',
|
|
421
579
|
middleware: [authMiddleware],
|
|
422
|
-
handler:
|
|
423
|
-
}
|
|
580
|
+
handler: () => ({ secret: 'data' })
|
|
581
|
+
})
|
|
424
582
|
]);
|
|
425
583
|
```
|
|
426
584
|
|
|
@@ -428,22 +586,42 @@ const routes = defineRoutes([
|
|
|
428
586
|
|
|
429
587
|
```typescript
|
|
430
588
|
const routes = defineRoutes([
|
|
431
|
-
{
|
|
589
|
+
defineRoute({
|
|
432
590
|
path: '/api',
|
|
433
591
|
middleware: [apiMiddleware],
|
|
434
592
|
children: [
|
|
435
|
-
{
|
|
436
|
-
|
|
437
|
-
|
|
593
|
+
defineRoute({
|
|
594
|
+
method: 'GET',
|
|
595
|
+
path: '/users',
|
|
596
|
+
handler: getUsers
|
|
597
|
+
}),
|
|
598
|
+
defineRoute({
|
|
599
|
+
method: 'POST',
|
|
600
|
+
path: '/users',
|
|
601
|
+
handler: createUser
|
|
602
|
+
}),
|
|
603
|
+
defineRoute({
|
|
438
604
|
path: '/users/:id',
|
|
439
605
|
children: [
|
|
440
|
-
{
|
|
441
|
-
|
|
442
|
-
|
|
606
|
+
defineRoute({
|
|
607
|
+
method: 'GET',
|
|
608
|
+
path: '/',
|
|
609
|
+
handler: getUser
|
|
610
|
+
}),
|
|
611
|
+
defineRoute({
|
|
612
|
+
method: 'PUT',
|
|
613
|
+
path: '/',
|
|
614
|
+
handler: updateUser
|
|
615
|
+
}),
|
|
616
|
+
defineRoute({
|
|
617
|
+
method: 'DELETE',
|
|
618
|
+
path: '/',
|
|
619
|
+
handler: deleteUser
|
|
620
|
+
}),
|
|
443
621
|
]
|
|
444
|
-
}
|
|
622
|
+
})
|
|
445
623
|
]
|
|
446
|
-
}
|
|
624
|
+
})
|
|
447
625
|
]);
|
|
448
626
|
```
|
|
449
627
|
|
|
@@ -509,7 +687,7 @@ precompileSchemas([UserSchema, PostSchema, CommentSchema]);
|
|
|
509
687
|
Vafast 内置 30+ 常用 format 验证器,**导入框架时自动注册**,对标 Zod 的内置验证:
|
|
510
688
|
|
|
511
689
|
```typescript
|
|
512
|
-
import {
|
|
690
|
+
import { defineRoute, defineRoutes, Type } from 'vafast';
|
|
513
691
|
|
|
514
692
|
// 直接使用内置 format,无需手动注册
|
|
515
693
|
const UserSchema = Type.Object({
|
|
@@ -520,9 +698,16 @@ const UserSchema = Type.Object({
|
|
|
520
698
|
createdAt: Type.String({ format: 'date-time' }),
|
|
521
699
|
});
|
|
522
700
|
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
701
|
+
const routes = defineRoutes([
|
|
702
|
+
defineRoute({
|
|
703
|
+
method: 'POST',
|
|
704
|
+
path: '/users',
|
|
705
|
+
schema: { body: UserSchema },
|
|
706
|
+
handler: ({ body }) => {
|
|
707
|
+
return { success: true, user: body };
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
]);
|
|
526
711
|
```
|
|
527
712
|
|
|
528
713
|
**支持的 Format 列表:**
|
|
@@ -551,52 +736,113 @@ registerFormat('order-id', (v) => /^ORD-\d{8}$/.test(v));
|
|
|
551
736
|
const isEmail = Patterns.EMAIL.test('test@example.com');
|
|
552
737
|
```
|
|
553
738
|
|
|
554
|
-
### 路由注册表 (RouteRegistry)
|
|
739
|
+
### 路由注册表 (RouteRegistry) — 声明式元数据,赋能业务逻辑
|
|
555
740
|
|
|
556
|
-
Vafast
|
|
741
|
+
Vafast 的声明式路由支持**任意扩展字段**,让路由定义成为业务逻辑的单一数据源。适用于 API 文档生成、Webhook 事件注册、权限检查、**按 API 计费**等场景:
|
|
557
742
|
|
|
558
743
|
```typescript
|
|
559
|
-
import { Server,
|
|
560
|
-
|
|
744
|
+
import { Server, defineRoute, defineRoutes, getRouteRegistry, defineMiddleware } from 'vafast';
|
|
745
|
+
|
|
746
|
+
// 计费中间件:基于路由元数据自动计费
|
|
747
|
+
const billingMiddleware = defineMiddleware(async (req, next) => {
|
|
748
|
+
const registry = getRouteRegistry();
|
|
749
|
+
const url = new URL(req.url);
|
|
750
|
+
const route = registry.get(req.method, url.pathname);
|
|
751
|
+
|
|
752
|
+
if (route?.billing) {
|
|
753
|
+
const { price, currency, unit } = route.billing;
|
|
754
|
+
const userId = getUserId(req);
|
|
755
|
+
|
|
756
|
+
// 执行计费逻辑
|
|
757
|
+
await chargeUser(userId, {
|
|
758
|
+
api: `${req.method} ${url.pathname}`,
|
|
759
|
+
price,
|
|
760
|
+
currency,
|
|
761
|
+
unit, // 'request' | 'token' | 'minute'
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return next();
|
|
766
|
+
});
|
|
561
767
|
|
|
562
768
|
// 定义带扩展字段的路由
|
|
563
|
-
const routes
|
|
564
|
-
{
|
|
769
|
+
const routes = defineRoutes([
|
|
770
|
+
defineRoute({
|
|
565
771
|
method: 'POST',
|
|
566
772
|
path: '/auth/signIn',
|
|
773
|
+
name: '用户登录',
|
|
774
|
+
description: '用户通过邮箱密码登录',
|
|
567
775
|
handler: signInHandler,
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
776
|
+
// ✨ 扩展字段:Webhook 事件
|
|
777
|
+
webhook: { eventKey: 'auth.signIn', enabled: true },
|
|
778
|
+
}),
|
|
779
|
+
defineRoute({
|
|
780
|
+
method: 'POST',
|
|
781
|
+
path: '/ai/generate',
|
|
782
|
+
name: 'AI 生成',
|
|
783
|
+
description: '生成 AI 内容',
|
|
784
|
+
// ✨ 扩展字段:按请求计费
|
|
785
|
+
billing: { price: 0.01, currency: 'USD', unit: 'request' },
|
|
786
|
+
// ✨ 扩展字段:权限要求
|
|
787
|
+
permission: 'ai.generate',
|
|
788
|
+
middleware: [billingMiddleware],
|
|
789
|
+
handler: async ({ body }) => {
|
|
790
|
+
return await generateAI(body.prompt);
|
|
791
|
+
}
|
|
792
|
+
}),
|
|
793
|
+
defineRoute({
|
|
794
|
+
method: 'POST',
|
|
795
|
+
path: '/ai/chat',
|
|
796
|
+
name: 'AI 对话',
|
|
797
|
+
// ✨ 扩展字段:按 token 计费
|
|
798
|
+
billing: { price: 0.0001, currency: 'USD', unit: 'token' },
|
|
799
|
+
permission: 'ai.chat',
|
|
800
|
+
middleware: [billingMiddleware],
|
|
801
|
+
handler: async ({ body }) => {
|
|
802
|
+
return await chatAI(body.message);
|
|
803
|
+
}
|
|
804
|
+
}),
|
|
805
|
+
defineRoute({
|
|
573
806
|
method: 'GET',
|
|
574
807
|
path: '/users',
|
|
575
808
|
handler: getUsersHandler,
|
|
576
|
-
permission: 'users.read',
|
|
577
|
-
|
|
578
|
-
|
|
809
|
+
permission: 'users.read',
|
|
810
|
+
// 免费 API,无需计费配置
|
|
811
|
+
}),
|
|
812
|
+
]);
|
|
579
813
|
|
|
580
814
|
const server = new Server(routes);
|
|
581
815
|
|
|
582
|
-
//
|
|
583
|
-
const registry =
|
|
816
|
+
// Server 创建时自动设置全局注册表,直接使用即可
|
|
817
|
+
const registry = getRouteRegistry();
|
|
584
818
|
|
|
585
|
-
//
|
|
586
|
-
const route = registry.get('POST', '/
|
|
587
|
-
console.log(route?.name);
|
|
819
|
+
// 查询路由元信息
|
|
820
|
+
const route = registry.get('POST', '/ai/generate');
|
|
821
|
+
console.log(route?.name); // 'AI 生成'
|
|
822
|
+
console.log(route?.billing); // { price: 0.01, currency: 'USD', unit: 'request' }
|
|
823
|
+
console.log(route?.permission); // 'ai.generate'
|
|
824
|
+
|
|
825
|
+
// 筛选有特定字段的路由
|
|
826
|
+
const webhookRoutes = registry.filter('webhook'); // 所有 Webhook 事件
|
|
827
|
+
const paidRoutes = registry.filter('billing'); // 所有付费 API
|
|
828
|
+
const aiRoutes = registry.filterBy(r => r.permission?.startsWith('ai.')); // AI 相关 API
|
|
588
829
|
|
|
589
830
|
// 按分类获取
|
|
590
831
|
const authRoutes = registry.getByCategory('auth');
|
|
591
|
-
|
|
592
|
-
// 筛选有特定字段的路由
|
|
593
|
-
const webhookRoutes = registry.filter('webhook');
|
|
594
|
-
const permissionRoutes = registry.filter('permission');
|
|
832
|
+
const aiCategoryRoutes = registry.getByCategory('ai');
|
|
595
833
|
|
|
596
834
|
// 获取所有分类
|
|
597
|
-
const categories = registry.getCategories(); // ['auth', 'users']
|
|
835
|
+
const categories = registry.getCategories(); // ['auth', 'ai', 'users']
|
|
598
836
|
```
|
|
599
837
|
|
|
838
|
+
**扩展字段的优势:**
|
|
839
|
+
|
|
840
|
+
1. **单一数据源**:路由定义包含所有元数据,无需额外配置文件
|
|
841
|
+
2. **类型安全**:扩展字段在 TypeScript 中完全类型化
|
|
842
|
+
3. **运行时查询**:通过 `RouteRegistry` API 动态查询和筛选
|
|
843
|
+
4. **业务集成**:中间件可直接读取路由元数据,实现计费、权限、审计等功能
|
|
844
|
+
5. **API 网关友好**:声明式配置完美适配网关场景
|
|
845
|
+
|
|
600
846
|
**Registry 实例方法:**
|
|
601
847
|
|
|
602
848
|
| 方法 | 说明 |
|
|
@@ -612,52 +858,61 @@ const categories = registry.getCategories(); // ['auth', 'users']
|
|
|
612
858
|
| `map(callback)` | 映射所有路由 |
|
|
613
859
|
| `size` | 路由数量 |
|
|
614
860
|
|
|
615
|
-
|
|
861
|
+
**全局便捷函数(Server 创建后自动可用):**
|
|
616
862
|
|
|
617
863
|
```typescript
|
|
618
864
|
import {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
865
|
+
getRouteRegistry, // 获取全局注册表实例
|
|
866
|
+
getRoute, // 快速查询单个路由
|
|
867
|
+
getAllRoutes, // 获取所有路由
|
|
868
|
+
filterRoutes, // 按字段筛选
|
|
869
|
+
getRoutesByMethod, // 按 HTTP 方法获取
|
|
623
870
|
} from 'vafast'
|
|
624
871
|
|
|
625
|
-
//
|
|
626
|
-
const
|
|
872
|
+
// 方式一:使用全局注册表实例
|
|
873
|
+
const registry = getRouteRegistry()
|
|
874
|
+
const route = registry.get('POST', '/users')
|
|
627
875
|
|
|
628
|
-
//
|
|
876
|
+
// 方式二:使用便捷函数(推荐,更简洁)
|
|
877
|
+
const route = getRoute('POST', '/users')
|
|
629
878
|
const allRoutes = getAllRoutes()
|
|
630
|
-
|
|
631
|
-
// 按字段筛选
|
|
632
879
|
const webhookRoutes = filterRoutes('webhook')
|
|
633
|
-
|
|
634
|
-
// 按 HTTP 方法获取
|
|
635
880
|
const getRoutes = getRoutesByMethod('GET')
|
|
636
881
|
const postRoutes = getRoutesByMethod('POST')
|
|
637
882
|
|
|
638
|
-
//
|
|
883
|
+
// 按路径前缀筛选
|
|
639
884
|
const authRoutes = getAllRoutes().filter(r => r.path.startsWith('/auth'))
|
|
640
885
|
```
|
|
641
886
|
|
|
887
|
+
> 💡 **提示**:Server 创建时会自动设置全局 RouteRegistry,无需手动创建。在任意文件中导入 `getRouteRegistry()` 即可访问。
|
|
888
|
+
|
|
642
889
|
### API Spec 生成
|
|
643
890
|
|
|
644
891
|
Vafast 提供 `getApiSpec` 用于生成 API 规范,支持跨仓库类型同步和 AI 工具函数生成:
|
|
645
892
|
|
|
646
893
|
```typescript
|
|
647
|
-
import { Server, defineRoutes, getApiSpec } from 'vafast';
|
|
894
|
+
import { Server, defineRoute, defineRoutes, getApiSpec } from 'vafast';
|
|
648
895
|
|
|
649
896
|
const routes = defineRoutes([
|
|
650
|
-
{
|
|
651
|
-
|
|
897
|
+
defineRoute({
|
|
898
|
+
method: 'GET',
|
|
899
|
+
path: '/users',
|
|
900
|
+
handler: getUsers
|
|
901
|
+
}),
|
|
902
|
+
defineRoute({
|
|
903
|
+
method: 'POST',
|
|
904
|
+
path: '/users',
|
|
905
|
+
handler: createUser
|
|
906
|
+
}),
|
|
907
|
+
// 添加 API Spec 接口
|
|
908
|
+
defineRoute({
|
|
909
|
+
method: 'GET',
|
|
910
|
+
path: '/api-spec',
|
|
911
|
+
handler: getApiSpec // 直接作为 handler
|
|
912
|
+
}),
|
|
652
913
|
]);
|
|
653
914
|
|
|
654
|
-
|
|
655
|
-
const allRoutes = [
|
|
656
|
-
...routes,
|
|
657
|
-
{ method: 'GET', path: '/api-spec', handler: getApiSpec } // 直接作为 handler
|
|
658
|
-
];
|
|
659
|
-
|
|
660
|
-
const server = new Server(allRoutes);
|
|
915
|
+
const server = new Server(routes);
|
|
661
916
|
```
|
|
662
917
|
|
|
663
918
|
**三种使用方式:**
|