weifuwu 0.33.9 → 0.33.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +654 -483
  2. package/dist/client/app.d.ts +1 -3
  3. package/dist/client/form.d.ts +111 -0
  4. package/dist/client/index.d.ts +78 -148
  5. package/dist/client/index.js +432 -790
  6. package/dist/client/jsx-runtime.d.ts +11 -1
  7. package/dist/client/jsx-runtime.js +432 -790
  8. package/dist/client/lazy.d.ts +50 -0
  9. package/dist/client/middleware/api.d.ts +71 -23
  10. package/dist/client/middleware/auth.d.ts +63 -20
  11. package/dist/client/middleware/ws.d.ts +2 -2
  12. package/dist/client/resource.d.ts +50 -0
  13. package/dist/client/router.d.ts +41 -7
  14. package/dist/client/signal.d.ts +0 -36
  15. package/dist/client/types.d.ts +35 -31
  16. package/dist/core/router.d.ts +21 -11
  17. package/dist/core/ws.d.ts +9 -12
  18. package/dist/graphql.d.ts +2 -1
  19. package/dist/index.d.ts +2 -35
  20. package/dist/index.js +367 -3586
  21. package/dist/types.d.ts +0 -12
  22. package/package.json +2 -1
  23. package/dist/ai/agent.d.ts +0 -127
  24. package/dist/ai/index.d.ts +0 -26
  25. package/dist/ai/types.d.ts +0 -59
  26. package/dist/base/index.d.ts +0 -59
  27. package/dist/base/module.d.ts +0 -42
  28. package/dist/base/types.d.ts +0 -116
  29. package/dist/client/components/Chat.d.ts +0 -19
  30. package/dist/client/components/Link.d.ts +0 -27
  31. package/dist/client/components/LoginForm.d.ts +0 -16
  32. package/dist/client/components/Transition.d.ts +0 -42
  33. package/dist/client/lib/css.d.ts +0 -33
  34. package/dist/client/lib/dev.d.ts +0 -28
  35. package/dist/client/lib/form.d.ts +0 -56
  36. package/dist/client/lib/model.d.ts +0 -36
  37. package/dist/client/lib/resource.d.ts +0 -56
  38. package/dist/cms/index.d.ts +0 -67
  39. package/dist/cms/module.d.ts +0 -38
  40. package/dist/cms/types.d.ts +0 -96
  41. package/dist/core/logger.d.ts +0 -16
  42. package/dist/core/trace.d.ts +0 -95
  43. package/dist/hub.d.ts +0 -36
  44. package/dist/kb/index.d.ts +0 -57
  45. package/dist/kb/module.d.ts +0 -37
  46. package/dist/kb/types.d.ts +0 -83
  47. package/dist/messager/index.d.ts +0 -72
  48. package/dist/messager/module.d.ts +0 -46
  49. package/dist/messager/types.d.ts +0 -99
  50. package/dist/middleware/compress.d.ts +0 -20
  51. package/dist/middleware/helmet.d.ts +0 -33
  52. package/dist/middleware/rate-limit.d.ts +0 -44
  53. package/dist/middleware/sandbox.d.ts +0 -52
  54. package/dist/middleware/upload.d.ts +0 -55
  55. package/dist/queue/cron.d.ts +0 -9
  56. package/dist/queue/index.d.ts +0 -12
  57. package/dist/queue/types.d.ts +0 -57
  58. package/dist/user/index.d.ts +0 -61
  59. package/dist/user/module.d.ts +0 -64
  60. package/dist/user/types.d.ts +0 -98
package/README.md CHANGED
@@ -1,651 +1,822 @@
1
1
  # weifuwu
2
2
 
3
- **AI SaaS full-stack framework** — `(req, ctx) => Response` + `(props, ctx) => JSX`
3
+ **全栈框架 后端 `(req, ctx) => Response` + 前端 `(props, ctx) => JSX`**
4
4
 
5
5
  ```bash
6
6
  npm install weifuwu
7
7
  ```
8
8
 
9
- One package. Backend + frontend. User system, messaging, RAG knowledge base, AI Agent, CMS, dynamic data storage, reactive frontend. Set env vars and go.
9
+ 一个包,无上游依赖。后端提供 HTTP 路由、数据库、中间件;前端提供信号驱动的无 VDOM 响应式框架。
10
10
 
11
11
  ---
12
12
 
13
- ## Core Concept: `ctx`
13
+ ## 模块总览
14
+
15
+ | 模块 | 导出 | 用途 | 依赖 |
16
+ |------|------|------|------|
17
+ | **Router** | `Router` | HTTP 路由 + 中间件链 + WebSocket + GraphQL | — |
18
+ | **serve** | `serve` | HTTP 服务器 | `Router` |
19
+ | **cors** | `cors` | CORS 跨域中间件 | `Router` |
20
+ | **serveStatic** | `serveStatic` | 静态文件服务 | `Router` |
21
+ | **postgres** | `postgres` | PostgreSQL 客户端 → `ctx.sql` | `Router` |
22
+ | **redis** | `redis` | Redis 客户端 → `ctx.redis` | `Router` |
23
+ | **ui** | `ui` | SSR 渲染 + 动态 JS 编译 → `ctx.ui.html/css/js` | `Router` |
24
+ | **graphql** | `router.graphql()` | GraphQL 端点 | `Router` |
25
+ | **client** | 28 个导出 | 前端响应式框架(见下方表格) | — |
26
+
27
+ **前端 `weifuwu/client` 模块总览:**
28
+
29
+ | 类别 | 导出 | 用途 |
30
+ |------|------|------|
31
+ | **信号系统** | `signal`, `computed`, `effect`, `batch`, `untrack`, `isSignal` | 响应式状态 |
32
+ | **JSX 运行时** | `jsx`/`jsxs`/`jsxDEV`, `Fragment` | TSX 编译目标 |
33
+ | **控制流** | `Show`, `For` | 条件/列表渲染 |
34
+ | **生命周期** | `onMount`, `onCleanup` | 组件挂载/卸载 |
35
+ | **应用** | `createApp` | 中间件链 + 挂载 |
36
+ | **路由** | `router`, `RouteView`, `lazy` | 嵌套布局 + 代码分割 + 滚动恢复 |
37
+ | **中间件** | `ws`, `api`, `auth` | WebSocket / HTTP 客户端 / 认证状态 |
38
+ | **工具** | `createResource`, `useForm`, `ErrorBoundary`, `createPortal`, `wrap`, `createContext`, `extendCtx`, `domMount` | 异步数据 / 表单 / 错误边界 / Portal |
39
+ | **类型 (19)** | `Signal`, `Component`, `WfuiContext`, `AppMiddleware`, `RouteDef`, `ApiClient`, `AuthClient`, `ResourceState`, `FormReturn`, 等 | — |
14
40
 
15
- **`ctx` 是整个框架的核心模式。** 后端和前端共享同一个理念:通过中间件向 `ctx` 注入能力,组件/handler 直接从 `ctx` 读取。
41
+ ---
16
42
 
17
- ```
18
- 后端: 前端:
19
- Request → 中间件链 → Handler createApp() → 中间件链 → 组件
20
- │ │
21
- ▼ ▼
22
- ctx.sql ctx.api
23
- ctx.user ctx.socket
24
- ctx.kb ctx.route
25
- ctx.agent ctx.user
26
- ctx.redis ctx.app.navigate
27
- ctx.messager ctx.provide/inject
28
- ctx.cms
29
- ctx.base
30
- ctx.queue
31
- ctx.ui
32
- ```
43
+ ## 核心理念:`ctx`
33
44
 
34
- **后端每个请求创建一个 ctx:**
35
- ```ts
36
- // 中间件注入 → handler 直接使用
37
- app.get('/api/chat', async (req, ctx) => {
38
- const user = ctx.user // 当前用户(auto-resolve from JWT)
39
- const result = await ctx.kb.search(query) // RAG 知识库
40
- return ctx.agent.chatStreamResponse({ messages }) // AI 流式响应
41
- })
42
- ```
45
+ 前后端共享同一模式:**中间件向 `ctx` 注入字段,handler/组件从 `ctx` 读取。**
43
46
 
44
- **前端每个组件通过第二个参数接收 ctx:**
45
- ```tsx
46
- function ChatPage(_props: {}, ctx: WfuiContext) {
47
- ctx.api.get('/api/posts') // HTTP 客户端
48
- ctx.socket.send(data) // WebSocket
49
- ctx.route.path // 当前路由
50
- ctx.app.navigate('/about') // 页面导航
51
- }
52
47
  ```
53
-
54
- 这种模式让开发者**无需 import 任何工具函数**,所有能力在 `ctx` 中一站获取。
55
-
56
- ---
57
-
58
- ## Module Overview
59
-
60
- ### Backend — 每个模块向 ctx 注入什么
61
-
62
- | Module | Import | Injects into `ctx` | Depends on | Purpose |
63
- |--------|--------|-------------------|------------|---------|
64
- | `postgres()` | `weifuwu` | `ctx.sql` | `DATABASE_URL` | PostgreSQL client |
65
- | `redis()` | `weifuwu` | `ctx.redis` | `REDIS_URL` | Redis client |
66
- | `user()` | `weifuwu` | `ctx.user`, `ctx.userModule` | `postgres()`, `JWT_SECRET` | Auth, JWT, roles |
67
- | `messager()` | `weifuwu` | `ctx.messager` | `postgres()`, `user()` | IM + AI conversation |
68
- | `kb()` | `weifuwu` | `ctx.kb` | `postgres()`, `DASHSCOPE_API_KEY` | RAG knowledge base |
69
- | `agent()` | `weifuwu` | `ctx.agent` | — | LLM chat, tools, streaming |
70
- | `cms()` | `weifuwu` | `ctx.cms` | `postgres()`, `user()` | Blog, docs, changelog |
71
- | `base()` | `weifuwu` | `ctx.base` | `postgres()`, `user()` | Dynamic data engine |
72
- | `queue()` | `weifuwu` | `ctx.queue` | `REDIS_URL` | Job queue + cron |
73
- | `ui()` | `weifuwu` | `ctx.ui.html/js/css` | — | SSR/SPA rendering |
74
-
75
- ### Backend Middleware
76
-
77
- | Middleware | Injects into `ctx` | Purpose |
78
- |-----------|-------------------|---------|
79
- | `cors()` | — | CORS headers |
80
- | `helmet()` | — | Security headers |
81
- | `compress()` | — | gzip / brotli / deflate |
82
- | `rateLimit()` | — | Sliding-window rate limiter |
83
- | `logger()` | — | Request logging |
84
- | `upload()` | `ctx.upload` | Multipart file upload |
85
- | `serveStatic()` | — | Static files |
86
- | `sandbox()` | — | Filesystem isolation |
87
-
88
- ### Frontend (`weifuwu/client`) — 每个中间件向 ctx 注入什么
89
-
90
- | Import | Type | Injects into `ctx` | Purpose |
91
- |--------|------|-------------------|---------|
92
- | `signal()` | function | — | Reactive state container |
93
- | `computed()` | function | — | Derived signals |
94
- | `effect()` | function | — | Auto-tracked side effects |
95
- | `batch()` | function | — | Batch multiple signal writes |
96
- | `untrack()` | function | — | Read signal without subscribing |
97
- | `onMount()` | function | — | Component mount callback |
98
- | `onCleanup()` | function | — | Component unmount callback |
99
- | `api()` | **middleware** | `ctx.api.get/post/...` | HTTP client |
100
- | `auth()` | **middleware** | `ctx.user/login/logout` | Auth state management |
101
- | `socket()` | **middleware** | `ctx.socket.send/onMessage/...` | WebSocket client |
102
- | `router()` | **middleware** | `ctx.route.path/params/query`, `ctx.app.navigate` | Hash/history router |
103
- | `createApp()` | function | — | App instance |
104
- | `<RouteView>` | component | — | Route outlet |
105
- | `<Show>` | component | — | Conditional rendering |
106
- | `<For>` | component | — | Keyed list rendering |
107
- | `<Transition>` | component | — | Animated enter/leave |
108
- | `<ErrorBoundary>` | component | — | Catch render errors |
109
- | `<Link>` | component | — | SPA navigation link |
110
- | `<LoginForm>` | component | — | Login/register form |
111
- | `<Chat>` | component | — | Real-time messaging |
112
- | `useForm()` | function | — | Form state management |
113
- | `useModel()` | function | — | Two-way form binding |
114
- | `reactiveArray()` | function | — | Reactive array with mut methods |
115
- | `createResource()` | function | — | Async data (loading/error/data) |
116
- | `createStyles()` | function | — | Scoped CSS |
117
- | `createContext()` | function | — | Type-safe provide/inject |
118
- | `createPortal()` | function | — | Render outside parent DOM |
119
- | `wrap()` | function | — | Third-party lib integration |
120
- | `enableDevtools()` | function | — | Dev warnings + browser inspector |
48
+ 后端: 前端:
49
+ Request Middleware Handler createApp() → Middleware → Component
50
+ │ │
51
+ ▼ ▼
52
+ ctx.sql ctx.ws
53
+ ctx.redis ctx.route
54
+ ctx.ui ctx.api / ctx.auth
55
+ ```
121
56
 
122
57
  ---
123
58
 
124
- ## Quick Start
125
-
126
- ### 完整全栈示例 — ctx 贯穿前后端
59
+ ## 快速开始 — 全栈应用
127
60
 
128
- **后端 `server.ts`:**
129
61
  ```ts
130
- import { serve, Router, postgres, user, agent, kb, messager, ui, cors, logger } from 'weifuwu'
131
- import { openai } from '@ai-sdk/openai'
62
+ // server.ts 同一个 npm
63
+ import { serve, Router, cors, ui } from 'weifuwu'
132
64
 
133
65
  const app = new Router()
134
66
  app.use(cors())
135
- app.use(logger())
136
- app.use(postgres()) // → ctx.sql
137
- app.use(user()) // ctx.user, ctx.userModule
138
- app.use(kb()) // ctx.kb
139
- app.use(agent({
140
- model: openai('deepseek-v4-flash', { baseURL: 'https://api.deepseek.com/v1' }),
141
- knowledge: { search: async (q, ctx) => ctx.kb.search(q) },
142
- })) // → ctx.agent
143
- app.use(messager()) // → ctx.messager
144
- app.use(ui()) // → ctx.ui
145
-
146
- // 业务 API — ctx 中所有能力可直接使用
147
- app.post('/api/chat', async (req, ctx) => {
148
- const { messages } = await req.json()
149
- return ctx.agent.chatStreamResponse({ messages })
67
+ app.use(ui())
68
+
69
+ // REST API
70
+ app.get('/api/posts', async (req, ctx) => {
71
+ const posts = [{ id: 1, title: 'Hello' }]
72
+ return Response.json(posts)
150
73
  })
151
74
 
152
- app.post('/api/messages', async (req, ctx) => {
153
- const msg = await ctx.messager.sendMessage(ctx.params.conversationId, req.body)
154
- return Response.json(msg, { status: 201 })
75
+ // WebSocket
76
+ app.ws('/ws', {
77
+ open(ws) { ws.send(JSON.stringify({ type: 'system', body: 'connected' })) },
78
+ message(ws, ctx, data) {
79
+ const msg = JSON.parse(data.toString())
80
+ ws.send(JSON.stringify({ type: 'echo', body: msg.body }))
81
+ },
155
82
  })
156
83
 
157
- // 客户端编译(无需本地构建步骤)
84
+ // SPA 入口 — 动态编译前端(零构建步骤)
85
+ app.get('/', async (req, ctx) => ctx.ui.html`
86
+ <!DOCTYPE html>
87
+ <html><body><div id="root"></div>
88
+ <script src="/static/app.js"></script></body></html>
89
+ `)
158
90
  app.get('/static/app.js', async (req, ctx) => ctx.ui.js('./src/main.tsx'))
159
91
 
160
92
  serve(app, { port: 3000 })
161
93
  ```
162
94
 
163
- **前端 `src/main.tsx` — ctx 驱动组件:**
164
95
  ```tsx
165
- import { signal, createApp, api, auth, socket, router, RouteView, LoginForm, Link } from 'weifuwu/client'
166
- import type { WfuiContext } from 'weifuwu/client'
96
+ // src/main.tsx 前端
97
+ import {
98
+ signal, computed, Show, For, ErrorBoundary, createPortal, wrap,
99
+ createApp, router, RouteView, lazy,
100
+ ws, api, auth, useForm, createResource,
101
+ } from 'weifuwu/client'
102
+ import type { WfuiContext, RouteDef } from 'weifuwu/client'
103
+
104
+ const app = createApp()
105
+ app.use(api({ baseURL: '' }))
106
+ app.use(auth())
107
+ app.use(ws())
108
+ app.use(router({
109
+ routes: [
110
+ { path: '/', component: Home },
111
+ {
112
+ path: '/dashboard',
113
+ layout: DashboardLayout,
114
+ children: [
115
+ { path: '/overview', component: lazy(() => import('./Overview')) },
116
+ { path: '/settings', component: lazy(() => import('./Settings')) },
117
+ ],
118
+ },
119
+ ],
120
+ mode: 'hash',
121
+ scrollRestoration: true,
122
+ }))
123
+ app.mount('#root', AppShell)
167
124
 
168
125
  function AppShell(_props: {}, ctx: WfuiContext) {
169
- if (!ctx.isAuthenticated) return <LoginForm />
170
126
  return (
171
127
  <div>
172
- <nav><Link to="/chat">Chat</Link></nav>
128
+ <nav>{/* ... */}</nav>
173
129
  <main><RouteView /></main>
174
130
  </div>
175
131
  )
176
132
  }
177
-
178
- const app = createApp()
179
- app.use(api()) // → ctx.api.get/post/...
180
- app.use(auth()) // → ctx.user/login/logout
181
- app.use(socket()) // → ctx.socket.send/onMessage
182
- app.use(router({ routes })) // → ctx.route.path/params/query
183
- app.mount('#root', AppShell)
184
- ```
185
-
186
- **`tsconfig.json`:**
187
- ```json
188
- { "jsx": "react-jsx", "jsxImportSource": "weifuwu/client" }
189
133
  ```
190
134
 
191
135
  ---
192
136
 
193
- ## Environment Variables
137
+ ## 后端
194
138
 
195
- | Variable | Default | Used by |
196
- |----------|---------|---------|
197
- | `DATABASE_URL` | `postgres://root:123456@localhost:5432/demo` | `postgres()` |
198
- | `REDIS_URL` | `redis://localhost:6379` | `redis()`, `queue()` |
199
- | `JWT_SECRET` | — | `user()` |
200
- | `DASHSCOPE_API_KEY` | — | `kb()` (embedding) |
201
- | `DEEPSEEK_API_KEY` / `OPENAI_API_KEY` | — | `agent()` (LLM) |
202
- | `DEEPSEEK_MODEL` | `deepseek-v4-flash` | `agent()` |
139
+ ### Router
203
140
 
204
- ---
141
+ ```ts
142
+ const app = new Router()
143
+
144
+ // HTTP 方法
145
+ app.get(path, ...handlers)
146
+ app.post / put / delete / patch / head / options(path, ...handlers)
147
+ app.all(path, ...handlers)
205
148
 
206
- ## Backend Modules
149
+ // WebSocket / GraphQL
150
+ app.ws(path, handler)
151
+ app.graphql('/graphql', handler)
207
152
 
208
- ### postgres
209
- ```ts
210
- import { postgres } from 'weifuwu'
211
- const sql = postgres()
212
- app.use(sql) // → ctx.sql
213
- await ctx.sql`SELECT * FROM users WHERE id = ${id}`
214
- await ctx.sql.begin(async (sql) => { /* transaction */ })
215
- ```
216
- | Method | Description |
217
- |--------|-------------|
218
- | `ctx.sql\`...\`` | Tagged template SQL |
219
- | `ctx.sql.begin(fn)` | Transaction |
220
- | `sql.close()` | Close pool |
221
-
222
- ### redis
223
- ```ts
224
- import { redis } from 'weifuwu'
225
- const r = redis()
226
- app.use(r) // → ctx.redis
227
- await ctx.redis.set('key', 'value')
228
- await ctx.redis.get('key')
229
- redis.close()
153
+ // 中间件
154
+ app.use(middleware)
155
+ app.mount(prefix, subRouter)
156
+ app.onError(handler)
157
+
158
+ // 调试
159
+ app.routes() // 列出所有路由
230
160
  ```
231
- Reads `REDIS_URL` env (default: `redis://localhost:6379`).
232
161
 
233
- ### user
162
+ | 方法 | 参数 | 说明 |
163
+ |------|------|------|
164
+ | `get/post/put/delete/patch/head/options` | `(path, ...handlers)` | 注册 HTTP 路由 |
165
+ | `all` | `(path, ...handlers)` | 匹配所有方法 |
166
+ | `ws` | `(path, handler)` | WebSocket 端点 |
167
+ | `graphql` | `('/path', handler)` | GraphQL 端点 |
168
+ | `use` | `(middleware)` | 全局中间件 |
169
+ | `mount` | `(prefix, subRouter)` | 子路由挂载 |
170
+ | `onError` | `(handler)` | 全局错误处理 |
171
+ | `routes` | `()` | 返回路由列表数组 |
172
+
173
+ ### serve — HTTP 服务器
174
+
234
175
  ```ts
235
- import { user, requireRole } from 'weifuwu'
236
- app.use(postgres())
237
- app.use(user({ secret: process.env.JWT_SECRET }))
238
- // → ctx.user (UserRecord | undefined), ctx.userModule
176
+ const srv = serve(app, { port: 3000 })
177
+ await srv.stop() // 程序化停止
178
+ // Ctrl+C / SIGTERM — 自动关闭所有连接后退出
179
+ ```
239
180
 
240
- // ctx.user Authorization Bearer token 或 token cookie 自动解析
241
- app.get('/api/me', async (req, ctx) => {
242
- if (!ctx.user) return new Response('Unauthorized', { status: 401 })
243
- return Response.json(ctx.user) // ctx.user.name / .email / .role
244
- })
181
+ | 选项 | 默认 | 说明 |
182
+ |------|------|------|
183
+ | `port` | `0` | 监听端口 |
184
+ | `hostname` | `'0.0.0.0'` | 监听地址 |
185
+ | `maxBodySize` | `10MB` | 请求体上限 |
186
+ | `timeout` | `30000` | Socket 超时 (ms) |
187
+ | `shutdown` | `true` | 是否注册 SIGTERM/SIGINT 处理 |
188
+
189
+ ### cors — CORS 中间件
190
+
191
+ ```ts
192
+ app.use(cors({
193
+ origin: ['https://example.com'],
194
+ credentials: true,
195
+ }))
245
196
  ```
246
- | `ctx.userModule.*` | Returns | Description |
247
- |--------------------|---------|-------------|
248
- | `register(input)` | `{ user, token }` | Register |
249
- | `login(email, pw)` | `{ user, token } \| null` | Login |
250
- | `getUserById(id)` | `UserRecord \| null` | Get by ID |
251
- | `updateUser(id, input)` | `UserRecord \| null` | Update |
252
- | `changePassword(id, oldPw, newPw)` | `boolean` | Change password |
253
- | `deleteUser(id)` | `boolean` | Soft delete |
254
- | `listUsers(inactive?)` | `UserRecord[]` | List users |
255
- | `verifyToken(token)` | `TokenPayload \| null` | Verify JWT |
256
197
 
257
- `requireRole('admin')`guard middleware. No auth → 401, wrong role → 403.
198
+ ### serveStatic 静态文件
258
199
 
259
- ### messager
260
200
  ```ts
261
- import { messager } from 'weifuwu'
262
- app.use(postgres())
263
- app.use(user())
264
- app.use(messager()) // ctx.messager
265
- ```
266
- | `ctx.messager.*` | Returns | Description |
267
- |------------------|---------|-------------|
268
- | `sendMessage(convId, body)` | `Message` | Send + WebSocket broadcast |
269
- | `getMessages(convId, opts?)` | `Message[]` | Cursor pagination |
270
- | `getConversations()` | `Conversation[]` | List with unread |
271
- | `createDirectConversation(userId)` | `Conversation` | Create/reuse DM |
272
- | `createGroupConversation(title, userIds)` | `Conversation` | Create group |
273
- | `editMessage(msgId, body)` | `Message \| null` | Edit (24h) |
274
- | `deleteMessage(msgId)` | `boolean` | Soft delete |
275
- | `markRead(convId)` | `void` | Mark as read |
276
-
277
- ### kb — Knowledge Base
201
+ app.use(serveStatic('./public', { prefix: '/assets' }))
202
+ ```
203
+
204
+ | 选项 | 默认 | 说明 |
205
+ |------|------|------|
206
+ | `prefix` | `''` | URL 前缀 |
207
+ | `index` | `'index.html'` | 默认首页 |
208
+
209
+ ### postgres PostgreSQL
210
+
278
211
  ```ts
279
- import { kb } from 'weifuwu'
280
212
  app.use(postgres())
281
- app.use(kb()) // → ctx.kb
213
+ // → ctx.sql`SELECT * FROM users`
214
+
215
+ app.use(postgres({ url: 'postgres://user:pass@host:5432/db' }))
216
+ // 默认读取 DATABASE_URL 环境变量
282
217
  ```
283
- | `ctx.kb.*` | Returns | Description |
284
- |------------|---------|-------------|
285
- | `importText(title, text)` | `{ document, chunks }` | Import → chunk → embed |
286
- | `search(query, opts?)` | `SearchResult[]` | Semantic search |
287
- | `list()` | `Document[]` | List documents |
288
- | `get(id)` | `Document \| null` | Get document |
289
- | `delete(id)` | `boolean` | Delete + cascade chunks |
290
218
 
291
- ### agent
219
+ | 选项 | 默认 | 说明 |
220
+ |------|------|------|
221
+ | `url` | `DATABASE_URL` | 连接字符串 |
222
+ | `migrations` | `'./migrations'` | 迁移文件目录 |
223
+
224
+ 依赖:需要 `postgres` npm 包。实现 `close(): Promise<void>`。
225
+
226
+ ### redis — Redis
227
+
292
228
  ```ts
293
- import { agent } from 'weifuwu'
294
- import { openai } from '@ai-sdk/openai'
295
- import { tool } from 'ai'
296
- import { z } from 'zod'
297
-
298
- app.use(agent({
299
- model: openai('deepseek-v4-flash', { baseURL: 'https://api.deepseek.com/v1' }),
300
- system: 'You are a helpful assistant.',
301
- knowledge: { search: async (q, ctx) => ctx.kb.search(q) },
302
- tools: {
303
- getWeather: tool({
304
- description: 'Get weather for a city',
305
- parameters: z.object({ city: z.string() }),
306
- execute: async ({ city }) => ({ temp: 22, unit: 'C' }),
307
- }),
308
- },
309
- maxSteps: 5,
310
- })) // → ctx.agent
229
+ app.use(redis())
230
+ // ctx.redis.get('key')
231
+ // ctx.redis.set('key', 'value')
232
+ // 默认读取 REDIS_URL 环境变量
311
233
  ```
312
- | `ctx.agent.*` | Description |
313
- |--------------|-------------|
314
- | `chat(prompt, opts?)` | Non-streaming, returns text |
315
- | `chatStreamResponse({ messages })` | SSE stream (useChat compatible) |
316
234
 
317
- ### cms
235
+ 依赖:需要 `ioredis` npm 包。实现 `close(): Promise<void>`。
236
+
237
+ ### ui — SSR + SPA 渲染
238
+
318
239
  ```ts
319
- import { cms, requireRole } from 'weifuwu'
320
- app.use(postgres())
321
- app.use(user())
322
- app.use(cms()) // ctx.cms
240
+ app.use(ui())
241
+
242
+ // SSR 页面
243
+ app.get('/blog/:slug', async (req, ctx) => ctx.ui.html`
244
+ <!DOCTYPE html>
245
+ <html><body><h1>${post.title}</h1></body></html>
246
+ `)
247
+
248
+ // 动态 JS 编译(esbuild,零构建步骤)
249
+ app.get('/app.js', async (req, ctx) => ctx.ui.js('./src/main.tsx'))
250
+
251
+ // CSS 编译(PostCSS + Tailwind)
252
+ app.get('/style.css', async (req, ctx) => ctx.ui.css('./src/style.css'))
323
253
  ```
324
- | `ctx.cms.*` | Returns | Description |
325
- |-------------|---------|-------------|
326
- | `create(input)` | `Content` | Create (admin) |
327
- | `get(slug)` | `Content \| null` | Get by slug |
328
- | `list(opts?)` | `Content[]` | List with filters |
329
- | `publish(id)` / `unpublish(id)` | `Content \| null` | Toggle status |
330
254
 
331
- ### base Dynamic Data Engine
255
+ | 方法 | 用途 |
256
+ |------|------|
257
+ | `ctx.ui.html\`...\`` | 渲染 HTML 模板(转义变量防 XSS) |
258
+ | `ctx.ui.html.unsafe(str)` | 插入原始 HTML |
259
+ | `ctx.ui.js(entryPath)` | 动态编译 TSX → JS bundle |
260
+ | `ctx.ui.css(entryPath)` | 编译 CSS (PostCSS + Tailwind) |
261
+
262
+ ### graphql — GraphQL
263
+
332
264
  ```ts
333
- app.use(base()) // ctx.base
334
- ctx.base.create({ name, tables })
335
- ctx.base.insert(baseId, table, data)
336
- ctx.base.query(baseId, table, { filter, limit })
265
+ app.graphql(async (req, ctx) => ({
266
+ schema: `type Query { hello: String }`,
267
+ resolvers: { Query: { hello: () => 'world' } },
268
+ graphiql: true,
269
+ }))
337
270
  ```
338
271
 
339
- ### queue
272
+ ### WebSocket
273
+
340
274
  ```ts
341
- const q = queue()
342
- app.use(q)
343
- q.process('email', async (job) => { ... })
344
- q.cron('cleanup', '0 3 * * *', () => ...)
345
- await q.add('email', { to: 'user@example.com' })
346
- q.run()
275
+ app.ws('/ws', {
276
+ open(ws, ctx) { ws.send('connected') },
277
+ message(ws, ctx, data) { /* data: string | Buffer */ },
278
+ close(ws, ctx) { /* cleanup */ },
279
+ error(ws, ctx, err) { /* log */ },
280
+ })
347
281
  ```
348
282
 
349
- ### ui — SSR & SPA
283
+ ### 错误处理
284
+
350
285
  ```ts
351
- app.use(ui()) // ctx.ui
352
- // SSR page
353
- app.get('/blog/:slug', async (req, ctx) => ctx.ui.html`<!DOCTYPE html>...`)
354
- // Dynamic JS compilation (no build step)
355
- app.get('/static/app.js', async (req, ctx) => ctx.ui.js('./src/main.tsx'))
356
- // CSS with Tailwind v4 support
357
- app.get('/static/style.css', async (req, ctx) => ctx.ui.css('./public/style.css'))
286
+ app.onError((err, req, ctx) => {
287
+ if (err instanceof HttpError) {
288
+ return new Response(err.message, { status: err.status })
289
+ }
290
+ console.error(err)
291
+ return new Response('Internal Server Error', { status: 500 })
292
+ })
358
293
  ```
359
294
 
360
- ---
295
+ | 类/常量 | 说明 |
296
+ |---------|------|
297
+ | `HttpError` | HTTP 错误 `new HttpError(msg, status)` |
298
+ | `DEFAULT_MAX_BODY` | 默认请求体上限 10MB |
299
+ | `MIGRATIONS_TABLE` | Postgres 迁移表名 |
361
300
 
362
- ## Frontend — weifuwu/client
301
+ ### 后端类型
363
302
 
364
- Reactive frontend framework. Zero virtual DOM, zero external dependencies.
303
+ `Context`, `Handler`, `Middleware`, `ErrorHandler`, `WebSocket`, `WebSocketHandler`, `ServeOptions`, `Server`, `CORSOptions`, `ServeStaticOptions`, `PostgresOptions`, `PostgresClient`, `PostgresInjected`, `RedisOptions`, `RedisClient`, `RedisInjected`, `GraphQLOptions`, `GraphQLHandler`
365
304
 
366
- ### Core APIs
305
+ ---
367
306
 
368
- ```tsx
369
- // Signal — reactive state
370
- const count = signal(0)
371
- const doubled = computed(() => count.value * 2)
372
- effect(() => console.log('count:', count.value))
307
+ ## 前端 (`weifuwu/client`)
373
308
 
374
- // Batch merge multiple writes
375
- batch(() => { a.value = 1; b.value = 2 })
309
+ **2750 行源码,28 个运行时导出 + 19 个类型,零外部依赖。**
376
310
 
377
- // Untrack — read without subscribing
378
- effect(() => { console.log(untrack(() => theme.value)) })
311
+ 构建配置(esbuild):
312
+ ```js
313
+ esbuild.build({
314
+ jsx: 'automatic',
315
+ jsxImportSource: 'weifuwu/client',
316
+ bundle: true,
317
+ })
318
+ ```
379
319
 
380
- // Lifecycle
381
- onMount(() => { init(); return () => cleanup() })
382
- onCleanup(() => clearInterval(id))
320
+ ### 信号系统
321
+
322
+ ```tsx
323
+ const count = signal(0) // 创建
324
+ const doubled = computed(() => count.value * 2) // 衍生
325
+ effect(() => console.log('count:', count.value)) // 副作用
326
+ batch(() => { a.value = 1; b.value = 2 }) // 批量更新
327
+ untrack(() => theme.value) // 不追踪依赖
328
+ isSignal(value) // 类型判断
383
329
  ```
384
330
 
385
- ### Reactive Array
331
+ | 函数 | 签名 | 说明 |
332
+ |------|------|------|
333
+ | `signal` | `<T>(initial: T) => Signal<T>` | 响应式数据容器 |
334
+ | `computed` | `<T>(fn: () => T) => Signal<T>` | 衍生值,自动缓存 |
335
+ | `effect` | `(fn: () => void) => dispose()` | 自动追踪依赖,变化时重跑 |
336
+ | `batch` | `(fn: () => void) => void` | 合并多次写入为一次通知 |
337
+ | `untrack` | `<T>(fn: () => T) => T` | 读取信号但不追踪依赖 |
338
+ | `isSignal` | `(value: unknown) => value is Signal` | 判断是否为 Signal |
339
+
340
+ ### JSX 运行时
386
341
 
387
342
  ```tsx
388
- const items = reactiveArray([1, 2, 3])
389
- items.push(4) // [1, 2, 3, 4]
390
- items.pop() // [1, 2, 3]
391
- items.remove(1) // [1, 3]
392
- items.clear() // []
393
- items.sort()
394
- items.reverse()
343
+ // 自动由 esbuild 调用(无需手动导入)
344
+ <div class="foo">hello</div>
345
+ <Fragment>...</Fragment>
395
346
  ```
396
347
 
397
- ### Control Flow
348
+ | 导出 | 说明 |
349
+ |------|------|
350
+ | `jsx` / `jsxs` / `jsxDEV` | JSX 编译目标 |
351
+ | `Fragment` | `<></>` 片段组件 |
352
+
353
+ **Signal 属性自动绑定:** `<input value={signalVal} />` — 信号变化时只更新对应 DOM 属性。
354
+
355
+ ### 控制流
398
356
 
399
357
  ```tsx
400
- // Show conditional rendering
401
- <Show when={isLoggedIn} fallback={<LoginPage />}>
358
+ <Show when={isLoggedIn} fallback={<Login />}>
402
359
  <Dashboard />
403
360
  </Show>
404
361
 
405
- // For keyed list rendering
406
- <For each={todos} keyBy="id">
407
- {(todo) => <TodoItem todo={todo} />}
362
+ <For each={items} keyBy="id">
363
+ {(item) => <div>{item.name}</div>}
408
364
  </For>
365
+ ```
409
366
 
410
- // Transition animated enter/leave
411
- <Transition show={isOpen} name="fade">
412
- <Modal />
413
- </Transition>
367
+ | 组件 | 属性 | 说明 |
368
+ |------|------|------|
369
+ | `Show` | `when: boolean \| Signal<boolean>`, `fallback?`, `children?` | 条件渲染,Signal 响应式切换 |
370
+ | `For` | `each: T[] \| Signal<T[]>`, `children: (item, index) => Node`, `keyBy?` | 列表渲染,支持 keyed 复用 |
414
371
 
415
- // ErrorBoundary — catch render errors
416
- <ErrorBoundary fallback={(e) => <p>{e.message}</p>} onError={reportError}>
417
- {() => <Dashboard />}
418
- </ErrorBoundary>
372
+ ### 生命周期
373
+
374
+ ```tsx
375
+ onMount(() => {
376
+ init()
377
+ return () => cleanup() // 自动清理
378
+ })
379
+ onCleanup(() => clearInterval(id))
419
380
  ```
420
381
 
421
- ### Form Handling
382
+ | 函数 | 说明 |
383
+ |------|------|
384
+ | `onMount(fn)` | 组件挂载后执行,返回函数在卸载时自动清理 |
385
+ | `onCleanup(fn)` | 组件卸载时执行 |
386
+
387
+ ### 应用
422
388
 
423
389
  ```tsx
424
- // useForm validation + submit
425
- const form = useForm({
426
- initial: { email: '', password: '' },
427
- validate: { email: (v) => !v && '必填' },
428
- validateOnInit: true,
429
- })
430
- <input {...form.field('email')} placeholder="邮箱" />
431
- <button onClick={() => form.submit(data => ctx.login(data))}>登录</button>
390
+ const app = createApp()
391
+ app.use(middleware1)
392
+ app.use(middleware2)
393
+ await app.mount('#root', AppShell)
394
+ ```
395
+
396
+ | 方法 | 说明 |
397
+ |------|------|
398
+ | `use(mw)` | 注册中间件,返回 `this` 支持链式 |
399
+ | `mount(selector, RootComponent)` | 挂载到 DOM |
400
+ | `hydrate(selector, Component, props?)` | 在 SSR 内容上附加组件 |
401
+ | `ctx` | 当前上下文 |
432
402
 
433
- // useModel — two-way binding (shorter)
434
- const name = signal('')
435
- const agreed = signal(false)
436
- <input {...useModel(name)} placeholder="姓名" />
437
- <input type="checkbox" {...useModel(agreed)} /> 同意
403
+ ### 路由
404
+
405
+ ```tsx
406
+ // 路由定义
407
+ const routes: RouteDef[] = [
408
+ { path: '/', component: HomePage },
409
+ {
410
+ path: '/dashboard',
411
+ layout: DashboardLayout, // 嵌套布局
412
+ children: [
413
+ { path: '/overview', component: Overview }, // 子路由
414
+ { path: '/settings', component: Settings },
415
+ ],
416
+ },
417
+ { path: '/user/:id', component: UserPage, title: '用户' },
418
+ ]
419
+
420
+ // 注册路由中间件
421
+ app.use(router({
422
+ routes,
423
+ notFound: NotFound,
424
+ mode: 'hash', // 'hash' | 'history'
425
+ scrollRestoration: true,
426
+ }))
427
+
428
+ // 路由出口 — 根层级和嵌套层级用同一个组件
429
+ function AppShell() {
430
+ return <main><RouteView /></main> // 根出口
431
+ }
432
+ function DashboardLayout() {
433
+ return (
434
+ <div class="flex">
435
+ <Sidebar />
436
+ <main><RouteView /></main> // 嵌套出口(同一组件)
437
+ </div>
438
+ )
439
+ }
438
440
  ```
439
441
 
440
- ### Async Data
442
+ | RouteDef 字段 | 类型 | 说明 |
443
+ |---------------|------|------|
444
+ | `path` | `string` | 路由路径,支持 `/:param` |
445
+ | `component` | `Component` | 路由组件 |
446
+ | `layout` | `Component` | 嵌套布局(渲染 `<RouteView />` 显示子路由)|
447
+ | `children` | `RouteDef[]` | 子路由(与 layout 配合使用)|
448
+ | `auth` | `boolean` | 是否需要登录 |
449
+ | `title` | `string` | 页面标题(自动设置 `document.title`)|
450
+ | `loader` | `(ctx) => Promise<data>` | 数据预取 → `ctx.route.data` |
451
+ | `transition` | `string` | 页面切换过渡动画 CSS class 前缀 |
452
+
453
+ | RouterOptions | 默认 | 说明 |
454
+ |---------------|------|------|
455
+ | `mode` | `'hash'` | 路由模式 |
456
+ | `notFound` | — | 404 组件 |
457
+ | `scrollRestoration` | `true` | 历史模式时恢复滚动位置 |
458
+ | `transition` | — | 全局过渡动画 |
459
+
460
+ **`ctx.route` 注入:**
441
461
 
442
462
  ```tsx
443
- const [posts, { loading, error, refetch }] = createResource(
444
- () => ctx.api.get('/api/posts'),
445
- { retry: 2, timeout: 5000 }, // optional
446
- )
463
+ ctx.route.path // '/user/42'
464
+ ctx.route.params // { id: '42' }
465
+ ctx.route.query // { tab: 'profile' }
466
+ ctx.route.component // 当前路由组件
467
+ ctx.route.data // loader 返回的数据
468
+ ctx.route.loading // loader 是否加载中
469
+ ctx.app.navigate('/path')
470
+ ```
471
+
472
+ ### 代码分割
473
+
474
+ ```tsx
475
+ const AdminPage = lazy(() => import('./pages/AdminPage'), {
476
+ fallback: () => <div>加载中...</div>,
477
+ })
447
478
 
448
- <Show when={loading}><Skeleton /></Show>
449
- <Show when={error}><Error /></Show>
450
- <For each={posts}>{(post) => <PostCard post={post} />}</For>
479
+ const routes = [
480
+ { path: '/admin', component: AdminPage },
481
+ ]
451
482
  ```
452
483
 
453
- ### Scoped CSS
484
+ esbuild `splitting: true` + `outdir`。
485
+
486
+ ### 中间件
487
+
488
+ #### ws — WebSocket 客户端
454
489
 
455
490
  ```tsx
456
- const s = createStyles({
457
- card: 'background: white; border-radius: 8px; padding: 16px;',
458
- title: 'font-size: 18px; color: #333;',
491
+ app.use(ws({ url: '/ws' }))
492
+
493
+ // 组件中:
494
+ onMount(() => {
495
+ const unsub = ctx.ws.onMessage((data) => { ... })
496
+ onCleanup(() => unsub())
459
497
  })
460
- <div class={s.card}><h2 class={s.title}>...</h2></div>
498
+ ctx.ws.send({ type: 'chat', body: 'hello' })
499
+ <Show when={ctx.ws.isConnected}>🟢 已连接</Show>
461
500
  ```
462
501
 
463
- ### Type-Safe Context
502
+ | `ctx.ws` | 类型 | 说明 |
503
+ |----------|------|------|
504
+ | `send` | `(data: unknown) => void` | 发送消息 |
505
+ | `onMessage` | `(handler) => dispose()` | 注册消息监听 |
506
+ | `isConnected` | `Signal<boolean>` | 连接状态信号 |
507
+
508
+ | 选项 | 默认 | 说明 |
509
+ |------|------|------|
510
+ | `url` | `'/ws'` | WebSocket 地址 |
511
+ | `reconnectInterval` | `3000` | 重连间隔 (ms) |
512
+ | `maxReconnect` | `10` | 最大重连次数 |
513
+
514
+ #### api — HTTP 客户端
464
515
 
465
516
  ```tsx
466
- const ThemeCtx = createContext<string>('theme')
467
- ThemeCtx.provide(ctx, 'dark') // 提供
468
- const theme = ThemeCtx.inject(ctx) // string | null
517
+ app.use(api({ baseURL: '/api' }))
518
+
519
+ // 组件中:
520
+ await ctx.api.get<User[]>('/users')
521
+ await ctx.api.post<User>('/users', body)
522
+ await ctx.api.put<User>('/users/1', body)
523
+ await ctx.api.patch<User>('/users/1', body)
524
+ await ctx.api.delete('/users/1')
469
525
  ```
470
526
 
471
- ### Third-Party Integration
527
+ | `ctx.api` | 签名 | 说明 |
528
+ |-----------|------|------|
529
+ | `get` | `<T>(url, opts?) => Promise<T>` | GET 请求 |
530
+ | `post` | `<T>(url, body?, opts?) => Promise<T>` | POST 请求 |
531
+ | `put` | `<T>(url, body?, opts?) => Promise<T>` | PUT 请求 |
532
+ | `patch` | `<T>(url, body?, opts?) => Promise<T>` | PATCH 请求 |
533
+ | `delete` | `<T>(url, opts?) => Promise<T>` | DELETE 请求 |
534
+
535
+ | 选项 | 说明 |
536
+ |------|------|
537
+ | `baseURL` | API 基础路径 |
538
+ | `headers` | 默认请求头 |
539
+ | `onRequest` | 请求拦截器 `({url, init}) => {url, init}` |
540
+ | `onResponse` | 响应拦截器 `(res) => Promise<T>` |
541
+
542
+ 错误类型:`ApiError` — 包含 `status` 和 `body`。
543
+
544
+ #### auth — 认证状态管理
472
545
 
473
546
  ```tsx
474
- const PieChart = wrap('div', (el, props, ctx) => {
475
- const chart = echarts.init(el)
476
- chart.setOption({ series: [{ type: 'pie', data: props.data }] })
477
- effect(() => chart.setOption(...))
478
- return () => chart.dispose()
547
+ app.use(auth())
548
+
549
+ // 组件中:
550
+ <Show when={ctx.auth.isLoggedIn} fallback={<Login />}>
551
+ <span>{ctx.auth.user.value?.name}</span>
552
+ <button onClick={() => ctx.auth.logout()}>退出</button>
553
+ </Show>
554
+
555
+ // 登录
556
+ ctx.auth.login('jwt-token', { id: 1, name: 'Alice' })
557
+ // 退出
558
+ ctx.auth.logout()
559
+ ```
560
+
561
+ | `ctx.auth` | 类型 | 说明 |
562
+ |-----------|------|------|
563
+ | `token` | `Signal<string \| null>` | 当前 token |
564
+ | `user` | `Signal<AuthUser \| null>` | 当前用户 |
565
+ | `isLoggedIn` | `Signal<boolean>` | 是否已登录(computed)|
566
+ | `login` | `(token, user) => void` | 存储 token + 用户到 localStorage |
567
+ | `logout` | `() => void` | 清除 token + 用户 |
568
+ | `setUser` | `(user) => void` | 更新用户信息 |
569
+ | `authorizationHeader` | `Signal<string \| null>` | `'Bearer xxx'` 或 `null` |
570
+
571
+ | 选项 | 默认 | 说明 |
572
+ |------|------|------|
573
+ | `storage` | `localStorage` | 存储方式 |
574
+ | `tokenKey` | `'weifuwu_token'` | token 存储 key |
575
+ | `userKey` | `'weifuwu_user'` | 用户信息存储 key |
576
+
577
+ ### 工具
578
+
579
+ #### useForm — 表单管理
580
+
581
+ ```tsx
582
+ const form = useForm({
583
+ initial: { name: '', email: '' },
584
+ validate: {
585
+ name: (v) => !v ? '请输入姓名' : null,
586
+ email: [
587
+ (v) => !v ? '请输入邮箱' : null,
588
+ (v) => !v.includes('@') ? '邮箱格式错误' : null,
589
+ ],
590
+ },
591
+ onSubmit: async (values) => {
592
+ await ctx.api.post('/users', values)
593
+ },
479
594
  })
480
- <Dashboard><PieChart data={salesData} /></Dashboard>
595
+
596
+ // JSX:
597
+ <form onSubmit={form.handleSubmit}>
598
+ <input {...form.field('name')} />
599
+ <span>{form.errors.value.name}</span>
600
+ <button disabled={form.submitting}>提交</button>
601
+ </form>
481
602
  ```
482
603
 
483
- ### Pre-built Components
604
+ | 返回值 | 类型 | 说明 |
605
+ |--------|------|------|
606
+ | `values` | `Signal<T>` | 表单值 |
607
+ | `errors` | `Signal<Partial<Record<keyof T, string\|null>>>` | 验证错误 |
608
+ | `submitting` | `Signal<boolean>` | 提交状态 |
609
+ | `touched` | `Signal<Partial<Record<keyof T, boolean>>>` | 触碰字段 |
610
+ | `handleSubmit` | `(e: Event) => void` | 提交处理(绑定到 `<form>`)|
611
+ | `field` | `(name) => { value, onInput, error }` | 字段绑定对象 |
612
+ | `setValue` | `(name, value) => void` | 设字段值 |
613
+ | `reset` | `() => void` | 重置表单 |
614
+ | `validateAll` | `() => boolean` | 触发全部验证 |
615
+
616
+ #### createResource — 异步数据
484
617
 
485
618
  ```tsx
486
- import { LoginForm, Chat, Link } from 'weifuwu/client'
619
+ const [data, { loading, error, refetch }] = createResource(
620
+ () => fetch('/api/posts').then(r => r.json()),
621
+ { initialValue: [] }
622
+ )
487
623
 
488
- <LoginForm /> // 登录/注册(自动切换模式)
489
- <Chat conversationId="123" /> // 实时消息聊天
490
- <Link to="/about">关于</Link> // SPA 导航(支持右键新标签页)
624
+ // JSX:
625
+ <Show when={loading}><p>加载中...</p></Show>
626
+ <Show when={error}><p>错误: {error.value?.message}</p></Show>
627
+ <Show when={computed(() => !loading.value && !error.value)}>
628
+ <For each={data}>{(item) => <div>{item.title}</div>}</For>
629
+ </Show>
491
630
  ```
492
631
 
493
- ### Router
632
+ | 返回值 | 类型 | 说明 |
633
+ |--------|------|------|
634
+ | `data` (元组第一项) | `Signal<T \| undefined>` | 数据信号 |
635
+ | `loading` | `Signal<boolean>` | 加载状态 |
636
+ | `error` | `Signal<Error \| undefined>` | 错误信号 |
637
+ | `refetch` | `() => void` | 手动重新加载 |
638
+
639
+ #### ErrorBoundary — 错误捕获
494
640
 
495
641
  ```tsx
496
- const routes: RouteDef[] = [
497
- { path: '/', component: HomePage, title: 'Home' },
498
- { path: '/post/:id', component: PostPage,
499
- loader: async (ctx) => ({ post: await ctx.api.get(`/api/posts/${ctx.route.params.id}`) }),
500
- },
501
- ]
642
+ <ErrorBoundary
643
+ fallback={(e) => <p>出错了: {e.message}</p>}
644
+ onError={(e) => console.error(e)}
645
+ >
646
+ {() => <Dashboard />} {/* 必须用 thunk */}
647
+ </ErrorBoundary>
648
+ ```
502
649
 
503
- app.use(router({ routes, notFound: NotFound, mode: 'hash', transition: 'page' }))
504
- // ctx.route.path / .params / .query / .data / .loading
650
+ #### createPortal 渲染到指定位置
505
651
 
506
- // 路由出口
507
- function AppShell() { return <main><RouteView /></main> }
652
+ ```tsx
653
+ <Show when={showModal}>
654
+ {createPortal(<Modal />, document.body)}
655
+ </Show>
508
656
  ```
509
657
 
510
- ### DevTools
658
+ #### wrap — 封装三方库为组件
511
659
 
512
- ```ts
513
- import { enableDevtools } from 'weifuwu/client'
514
- if (import.meta.env.DEV) enableDevtools()
660
+ ```tsx
661
+ const Chart = wrap('div', (el, props: { data: any }, ctx) => {
662
+ const chart = echarts.init(el)
663
+ chart.setOption(props.data)
664
+ effect(() => chart.setOption(props.data))
665
+ return () => chart.dispose() // 卸载时自动清理
666
+ })
515
667
 
516
- // 浏览器控制台:
517
- // __wefu__.inspect() → 查看所有 signal
518
- // __wefu__.warnings() → 切换开发警告
668
+ // 使用:
669
+ <Chart data={salesData} />
519
670
  ```
520
671
 
521
- ---
672
+ #### createContext / extendCtx — 上下文扩展
522
673
 
523
- ## From React to weifuwu/client
674
+ ```tsx
675
+ // 类型安全的 provide/inject
676
+ const ThemeCtx = createContext<string>('theme')
677
+ ThemeCtx.provide(ctx, 'dark')
678
+ const theme = ThemeCtx.inject(ctx) // 'dark' | null
679
+
680
+ // 中间件注入
681
+ function myMiddleware(): AppMiddleware {
682
+ return (ctx) => extendCtx(ctx, { myField: 'hello' })
683
+ }
684
+ ```
685
+
686
+ ### React 对照表
524
687
 
525
688
  | React | weifuwu/client |
526
689
  |-------|----------------|
527
690
  | `useState(0)` | `signal(0)` |
528
- | `useMemo(() => a * 2, [a])` | `computed(() => a.value * 2)` |
529
- | `useEffect(() => {...}, [])` | `effect(() => {...})` + `onCleanup(() => {...})` |
530
- | `{condition && <X/>}` | `<Show when={condition}><X/></Show>` |
531
- | `{items.map(i => <X/>)}` | `<For each={items}>{(i) => <X/>}</For>` |
532
- | `className={...}` | `class={...}` |
533
- | Context Provider | `ctx.provide(key, val)` / `createContext()` |
534
- | `useNavigate()` | `ctx.app.navigate(path)` |
691
+ | `useMemo(() => a*2, [a])` | `computed(() => a.value * 2)` |
692
+ | `useEffect(() => f, [])` | `onMount(f)` |
693
+ | `useEffect(() => f, [dep])` | `effect(f)` |
694
+ | `{cond && <X/>}` | `<Show when={cond}><X/></Show>` |
695
+ | `{arr.map(i => <X/>)}` | `<For each={arr}>{(i) => <X/>}</For>` |
696
+ | `Suspense` | `<Show when={!loading}>` |
697
+ | `ErrorBoundary` | `<ErrorBoundary>` |
698
+ | `createPortal` | `createPortal` |
699
+ | `useNavigate()` | `ctx.app.navigate()` |
535
700
  | `useParams()` | `ctx.route.params` |
536
- | `fetch / axios` | `ctx.api.get/post/...` |
537
- | WebSocket | `ctx.socket.send/onMessage` |
701
+ | `useFormik()` | `useForm()` |
702
+ | `axios.get()` | `ctx.api.get()` |
703
+ | `useSWR()` | `createResource()` |
704
+ | `React.lazy()` | `lazy()` |
705
+ | `useContext()` | `createContext()` |
706
+
707
+ ### 前端类型
538
708
 
539
- **What you DON'T need:**
540
- - No hooks rules — no `use` prefix, no rules-of-hooks
541
- - No virtual DOM — JSX creates real DOM directly
542
- - No dependency arrays — `effect()` auto-tracks
543
- - No state management library — signal is the state management
544
- - No Context.Provider component — just `ctx.provide/inject`
709
+ `Signal`, `Component`, `WfuiContext`, `AppMiddleware`, `RouteDef`, `ApiClient`, `ApiOptions`, `ApiRequestOptions`, `AuthClient`, `AuthUser`, `AuthOptions`, `ResourceOptions`, `ResourceState`, `FormOptions`, `FormReturn`, `FormFieldBindings`, `FormValidators`, `LazyComponentOptions`, `LazyStatus`
545
710
 
546
711
  ---
547
712
 
548
- ## Utils
713
+ ## 全栈模式
549
714
 
550
- | Import | Purpose |
551
- |--------|---------|
552
- | `requireRole('admin')` | Middleware factory: check `ctx.user.role` |
553
- | `createHub()` | WebSocket pub/sub |
554
- | `HttpError` | `throw new HttpError(msg, 404)` |
555
- | `trace()` | Request tracing |
715
+ ### 认证流程
556
716
 
557
- ---
717
+ ```ts
718
+ // 后端
719
+ app.post('/api/login', async (req, ctx) => {
720
+ const { email } = await req.json()
721
+ return Response.json({
722
+ token: 'jwt_' + Math.random().toString(36),
723
+ user: { id: 1, name: email.split('@')[0], email },
724
+ })
725
+ })
558
726
 
559
- ## Router (Backend)
727
+ // 前端
728
+ app.use(api({ baseURL: '' }))
729
+ app.use(auth())
560
730
 
561
- ```ts
562
- const app = new Router()
563
- app.get(path, ...handlers)
564
- app.post / put / delete / patch / head / options(path, ...handlers)
565
- app.all(path, ...handlers)
566
- app.ws(path, ...handler) // WebSocket
567
- app.use(middleware) // Global middleware
568
- app.mount(prefix, router) // Sub-router
569
- app.plugin(fn) // Plugin
570
- app.onError(handler) // Error handler
571
- app.routes() // Debug: list all routes
731
+ // 登录
732
+ const res = await ctx.api.post('/api/login', { email, password })
733
+ ctx.auth.login(res.token, res.user)
572
734
  ```
573
735
 
574
- ---
736
+ ### 异步数据 + SSR
575
737
 
576
- ## Project Structure
738
+ ```ts
739
+ // 后端 — 同路径既支持 SSR 也支持 API
740
+ app.get('/api/posts', async (req, ctx) => {
741
+ return Response.json(posts)
742
+ })
577
743
 
744
+ // 前端 — 客户端获取
745
+ const [posts, { loading }] = createResource(
746
+ () => ctx.api.get('/api/posts')
747
+ )
578
748
  ```
579
- src/
580
- ├── index.ts ← Entry, exports all modules
581
- ├── types.ts ← Context, Handler, Middleware types
582
- ├── core/ ← serve, router, ws, trace, logger
583
- ├── middleware/ ← cors, helmet, compress, rate-limit, upload, static, sandbox
584
- ├── user/ ← User system (CRUD, JWT, requireRole)
585
- ├── messager/ ← IM + AI conversation layer
586
- ├── kb/ ← RAG knowledge base
587
- ├── ai/ ← AI Agent (LLM, tools, RAG)
588
- ├── cms/ Content management
589
- ├── base/ Dynamic data engine
590
- ├── postgres/ ← PostgreSQL client
591
- ├── redis/ ← Redis client
592
- ├── queue/ ← Job queue + cron
593
- ├── graphql.ts ← GraphQL
594
- ├── hub.ts ← WebSocket hub
595
- ├── ui/ ← ctx.ui.html / ctx.ui.js / ctx.ui.css
596
- ├── client/ ← Frontend framework
597
- │ ├── index.ts ← Exports all client APIs (33 exports)
598
- │ ├── signal.ts ← Signal / effect / computed / batch / untrack
599
- │ ├── jsx-runtime.ts ← JSX → DOM + types + Show/For/wrap/ErrorBoundary/Portal/Transition
600
- │ ├── app.ts ← createApp / hydrate / middleware chain
601
- │ ├── router.ts ← Route matching / RouteView / loader / transition
602
- │ ├── types.ts ← WfuiContext / RouteDef / createContext
603
- │ ├── lib/
604
- │ │ ├── form.ts ← useForm (validated forms)
605
- │ │ ├── model.ts ← useModel (two-way binding)
606
- │ │ ├── resource.ts ← createResource (async data)
607
- │ │ ├── css.ts ← createStyles (scoped CSS)
608
- │ │ └── dev.ts ← enableDevtools (dev warnings)
609
- │ ├── middleware/
610
- │ │ ├── api.ts ← HTTP client → ctx.api
611
- │ │ ├── auth.ts ← Auth → ctx.user/login/logout
612
- │ │ └── ws.ts ← WebSocket → ctx.socket
613
- │ └── components/
614
- │ ├── LoginForm.tsx ← Login/register
615
- │ ├── Chat.tsx ← Real-time chat
616
- │ ├── Link.tsx ← SPA navigation
617
- │ └── Transition.tsx ← Animated enter/leave
618
- └── test/ ← 66 unit tests + 10 benchmarks
619
-
620
- apps/demo/ ← Full-stack demo
621
- ├── server.ts ← weifuwu server (user, kb, agent, messager 未演示)
622
- ├── src/main.tsx ← SPA + SSR hydrate demo pages
623
- ├── public/ ← style.css (Tailwind + transition classes)
624
- └── tsconfig.json
625
-
626
- docker-compose.yml ← postgres (pgvector) + redis
749
+
750
+ ### 嵌套布局 + 代码分割
751
+
752
+ ```tsx
753
+ const routes = [
754
+ {
755
+ path: '/dashboard',
756
+ layout: DashboardLayout, // 侧边栏等 UI 保持挂载
757
+ children: [
758
+ { path: '/overview', component: lazy(() => import('./Overview')) },
759
+ { path: '/settings', component: lazy(() => import('./Settings')) },
760
+ ],
761
+ },
762
+ ]
627
763
  ```
628
764
 
629
765
  ---
630
766
 
631
- ## Development
767
+ ## 环境变量
768
+
769
+ | 变量 | 默认 | 说明 |
770
+ |------|------|------|
771
+ | `DATABASE_URL` | `postgres://root:123456@localhost:5432/demo` | Postgres 连接字符串 |
772
+ | `REDIS_URL` | `redis://localhost:6379` | Redis 连接字符串 |
773
+
774
+ ---
775
+
776
+ ## Demo
632
777
 
633
778
  ```bash
634
- docker compose up -d # Start postgres + redis
635
- npm run build # esbuild → dist/
636
- npm run typecheck # tsc --noEmit
637
- npm test # Run all tests
779
+ cd apps/demo
780
+ node server.ts
781
+ # http://localhost:3000
638
782
  ```
639
783
 
784
+ Demo 包含:嵌套布局、signal 待办列表、useForm 表单、createResource 数据请求、api + auth 认证、WebSocket 实时通信。
785
+
640
786
  ---
641
787
 
642
- ## Performance (Client Benchmarks)
788
+ ## 项目结构
643
789
 
644
- | Operation | Throughput |
645
- |-----------|-----------|
646
- | Signal creation | ~10,000 ops/ms |
647
- | Signal read/write | ~9,600 ops/ms |
648
- | Notify 10,000 effects | ~2,600 ops/ms |
649
- | batch merge 10,000 writes | ~0.6ms |
650
- | JSX div creation | ~200 ops/ms |
651
- | For render 10,000 items | ~109 ops/ms |
790
+ ```
791
+ src/
792
+ ├── index.ts 入口,导出所有后端模块
793
+ ├── types.ts Context, Handler, Middleware 等类型
794
+ ├── core/ Router, serve, WebSocket upgrade
795
+ ├── middleware/ cors, serveStatic
796
+ ├── postgres/ PostgreSQL 客户端
797
+ ├── redis/ Redis 客户端
798
+ ├── ui/ SSR 渲染 + 动态编译
799
+ ├── graphql.ts GraphQL
800
+ ├── client/
801
+ │ ├── index.ts 前端导出入口
802
+ │ ├── signal.ts 响应式系统
803
+ │ ├── jsx-runtime.ts JSX → DOM, Show/For/ErrorBoundary/Portal
804
+ │ ├── router.ts 路由中间件 + RouteView
805
+ │ ├── app.ts createApp 应用实例
806
+ │ ├── resource.ts createResource 异步数据
807
+ │ ├── form.ts useForm 表单
808
+ │ ├── lazy.ts 组件懒加载
809
+ │ ├── types.ts 前端类型
810
+ │ └── middleware/
811
+ │ ├── ws.ts WebSocket 客户端
812
+ │ ├── api.ts HTTP 客户端
813
+ │ └── auth.ts 认证状态管理
814
+ ├── test/ 测试
815
+ apps/demo/ 全栈 demo
816
+ ```
817
+
818
+ ```bash
819
+ npm run build # esbuild → dist/
820
+ npm run typecheck # tsc --noEmit
821
+ npm test # 运行所有测试
822
+ ```