weifuwu 0.33.0 → 0.33.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 CHANGED
@@ -1,17 +1,19 @@
1
1
  # weifuwu
2
2
 
3
- **AI SaaS 框架** — `(req, ctx) => Response`
3
+ **AI SaaS full-stack framework** — `(req, ctx) => Response` + `(props, ctx) => JSX`
4
4
 
5
5
  ```bash
6
6
  npm install weifuwu
7
7
  ```
8
8
 
9
- 用户系统、即时消息、RAG 知识库、AI Agent、内容管理、动态数据存储。配好环境变量就能跑。
9
+ One package. Backend + frontend. User system, instant messaging, RAG knowledge base, AI Agent, CMS, dynamic data storage, and a reactive frontend framework. Configure environment variables and go.
10
10
 
11
11
  ---
12
12
 
13
13
  ## Quick Start
14
14
 
15
+ ### Backend
16
+
15
17
  ```ts
16
18
  import { serve, Router, postgres, user, kb, agent, messager } from 'weifuwu'
17
19
  import { openai } from '@ai-sdk/openai'
@@ -34,7 +36,47 @@ app.post('/api/chat', async (req, ctx) => {
34
36
  serve(app, { port: 3000 })
35
37
  ```
36
38
 
37
- ### 环境变量
39
+ ### Frontend
40
+
41
+ ```tsx
42
+ import { signal, Show, For, createApp, router, RouteView } from 'weifuwu/client'
43
+ import type { WfuiContext } from 'weifuwu/client'
44
+
45
+ function HomePage(_props: {}, ctx: WfuiContext) {
46
+ return (
47
+ <div>
48
+ <h1>Hello weifuwu</h1>
49
+ <p>当前路径: {ctx.route.path}</p>
50
+ </div>
51
+ )
52
+ }
53
+
54
+ const app = createApp()
55
+ app.use(router({
56
+ routes: [
57
+ { path: '/', component: HomePage, title: '首页' },
58
+ { path: '/chat/:id', component: ChatPage, title: '聊天' },
59
+ ],
60
+ }))
61
+ app.mount('#root', AppShell)
62
+ ```
63
+
64
+ ```json
65
+ // tsconfig.json
66
+ { "jsx": "react-jsx", "jsxImportSource": "weifuwu/client" }
67
+ ```
68
+
69
+ ```js
70
+ // build.mjs
71
+ esbuild.build({
72
+ entryPoints: ['src/main.tsx'],
73
+ jsx: 'automatic',
74
+ jsxImportSource: 'weifuwu/client',
75
+ bundle: true,
76
+ })
77
+ ```
78
+
79
+ ### Environment Variables
38
80
 
39
81
  | Variable | Default | Used by |
40
82
  |----------|---------|---------|
@@ -49,14 +91,16 @@ serve(app, { port: 3000 })
49
91
 
50
92
  ## Modules
51
93
 
94
+ ### Backend
95
+
52
96
  | Module | Import | Dependency | Purpose |
53
97
  |--------|--------|-----------|---------|
54
- | User | `user()` | `postgres()` | 注册、登录、JWT、角色 |
55
- | Messager | `messager()` | `postgres()`, `user()` | 即时消息、AI 对话交互层 |
56
- | KB | `kb()` | `postgres()` | RAG 知识库、分片、向量搜索 |
57
- | Agent | `agent()` | — | LLM 对话、工具调用、流式输出 |
58
- | CMS | `cms()` | `postgres()`, `user()` | 博客、文档、公告 |
59
- | Base | `base()` | `postgres()`, `user()` | 动态数据存储引擎 |
98
+ | User | `user()` | `postgres()` | Auth, JWT, roles |
99
+ | Messager | `messager()` | `postgres()`, `user()` | IM + AI conversation layer |
100
+ | KB | `kb()` | `postgres()` | RAG knowledge base |
101
+ | Agent | `agent()` | — | LLM chat, tool calling, streaming |
102
+ | CMS | `cms()` | `postgres()`, `user()` | Blog, docs, changelog |
103
+ | Base | `base()` | `postgres()`, `user()` | Dynamic data engine |
60
104
 
61
105
  ### Middleware
62
106
 
@@ -84,6 +128,20 @@ serve(app, { port: 3000 })
84
128
  | `queue()` | Job queue + cron |
85
129
  | `createHub()` | WebSocket pub/sub |
86
130
 
131
+ ### Frontend (weifuwu/client)
132
+
133
+ | Import | Purpose |
134
+ |--------|---------|
135
+ | `signal()` | Reactive state |
136
+ | `computed()` | Derived signals |
137
+ | `effect()` | Auto-tracked side effects |
138
+ | `<Show>` | Conditional rendering |
139
+ | `<For>` | List rendering |
140
+ | `<RouteView>` | Route outlet |
141
+ | `createApp()` | App instance with middleware chain |
142
+ | `router()` | Hash/history router with params + query |
143
+ | `domMount()` | Direct DOM mounting |
144
+
87
145
  ### Utilities
88
146
 
89
147
  | Import | Purpose |
@@ -92,9 +150,95 @@ serve(app, { port: 3000 })
92
150
 
93
151
  ---
94
152
 
153
+ ## Frontend (weifuwu/client)
154
+
155
+ **weifuwu/client** is a reactive frontend framework built on Signal + TSX. Zero virtual DOM, zero dependencies, ~600 lines total.
156
+
157
+ ### Concepts
158
+
159
+ ```ts
160
+ // 1. Signal — reactive data
161
+ const count = signal(0)
162
+ count.value = count.value + 1 // DOM updates automatically
163
+
164
+ // 2. Computed — derived signals
165
+ const doubled = computed(() => count.value * 2)
166
+
167
+ // 3. Effect — auto-tracked side effects
168
+ effect(() => console.log('count:', count.value))
169
+
170
+ // 4. Component — (props, ctx) => JSX
171
+ function MyComponent({ name }: { name: string }, ctx: WfuiContext) {
172
+ return <div>Hello {name}</div>
173
+ }
174
+ ```
175
+
176
+ ### createApp + Middleware
177
+
178
+ ```tsx
179
+ import { createApp, router, RouteView } from 'weifuwu/client'
180
+ import type { WfuiContext, RouteDef } from 'weifuwu/client'
181
+
182
+ const app = createApp()
183
+ app.use(router({ routes, mode: 'hash' }))
184
+ app.mount('#root', AppShell)
185
+ ```
186
+
187
+ ### Router
188
+
189
+ ```tsx
190
+ const routes: RouteDef[] = [
191
+ { path: '/', component: HomePage, title: '首页' },
192
+ { path: '/chat/:id', component: ChatPage, title: '聊天' },
193
+ { path: '/user/:name', component: UserPage, title: '用户' },
194
+ ]
195
+
196
+ app.use(router({
197
+ routes,
198
+ notFound: NotFound,
199
+ mode: 'hash', // or 'history'
200
+ }))
201
+
202
+ // In layout:
203
+ function AppShell(_, ctx) {
204
+ return (
205
+ <div>
206
+ <nav>
207
+ <a onClick={() => ctx.app.navigate('/')}>首页</a>
208
+ <a onClick={() => ctx.app.navigate('/chat/123')}>聊天</a>
209
+ </nav>
210
+ <main>
211
+ <RouteView /> {/* ← renders matched route */}
212
+ </main>
213
+ </div>
214
+ )
215
+ }
216
+
217
+ // Route params and query:
218
+ ctx.route.path // "/chat/123"
219
+ ctx.route.params // { id: "123" }
220
+ ctx.route.query // { tab: "settings" }
221
+ ```
222
+
223
+ ### Show / For
224
+
225
+ ```tsx
226
+ // Conditional rendering (supports Signal)
227
+ <Show when={isLoggedIn} fallback={<LoginPage />}>
228
+ <Dashboard />
229
+ </Show>
230
+
231
+ // List rendering (supports Signal)
232
+ <For each={filteredItems}>
233
+ {(item) => <div>{item.name}</div>}
234
+ </For>
235
+ ```
236
+
237
+ ---
238
+
95
239
  ## user
96
240
 
97
- 身份认证、注册登录、JWT、密码管理。
241
+ Auth, registration, JWT, password management, roles.
98
242
 
99
243
  ```ts
100
244
  import { user, requireRole } from 'weifuwu'
@@ -102,24 +246,24 @@ import { user, requireRole } from 'weifuwu'
102
246
  app.use(postgres())
103
247
  app.use(user({ secret: process.env.JWT_SECRET }))
104
248
 
105
- // 注册
249
+ // Register
106
250
  app.post('/api/register', async (req, ctx) => {
107
251
  const result = await ctx.userModule.register(await req.json())
108
252
  return Response.json(result)
109
253
  })
110
- // 登录
254
+ // Login
111
255
  app.post('/api/login', async (req, ctx) => {
112
256
  const { email, password } = await req.json()
113
257
  const result = await ctx.userModule.login(email, password)
114
258
  if (!result) return new Response('Unauthorized', { status: 401 })
115
259
  return Response.json(result)
116
260
  })
117
- // 当前用户
261
+ // Current user
118
262
  app.get('/api/me', async (req, ctx) => {
119
263
  if (!ctx.user) return new Response('Unauthorized', { status: 401 })
120
264
  return Response.json(ctx.user)
121
265
  })
122
- // 仅管理员
266
+ // Admin only
123
267
  app.get('/api/admin/users', requireRole('admin'), async (req, ctx) => {
124
268
  return Response.json(await ctx.userModule.listUsers())
125
269
  })
@@ -129,28 +273,28 @@ app.get('/api/admin/users', requireRole('admin'), async (req, ctx) => {
129
273
 
130
274
  | Method | Returns | Description |
131
275
  |--------|---------|-------------|
132
- | `register(input)` | `{ user, token }` | 注册 |
133
- | `login(email, pw)` | `{ user, token } \| null` | 登录 |
134
- | `getUserById(id)` | `UserRecord \| null` | 查用户 |
135
- | `getUserByEmail(email)` | `UserRecord \| null` | 查邮箱 |
136
- | `updateUser(id, input)` | `UserRecord \| null` | 更新 |
137
- | `changePassword(id, oldPw, newPw)` | `boolean` | 改密码 |
138
- | `deleteUser(id)` | `boolean` | 软删除 |
139
- | `listUsers(inactive?)` | `UserRecord[]` | 用户列表 |
140
- | `generateToken(user)` | `string` | 签发 JWT |
141
- | `verifyToken(token)` | `TokenPayload \| null` | 验证 JWT |
142
- | `refreshToken(token)` | `string \| null` | 刷新 JWT |
276
+ | `register(input)` | `{ user, token }` | Register |
277
+ | `login(email, pw)` | `{ user, token } \| null` | Login |
278
+ | `getUserById(id)` | `UserRecord \| null` | Get by ID |
279
+ | `getUserByEmail(email)` | `UserRecord \| null` | Get by email |
280
+ | `updateUser(id, input)` | `UserRecord \| null` | Update |
281
+ | `changePassword(id, oldPw, newPw)` | `boolean` | Change password |
282
+ | `deleteUser(id)` | `boolean` | Soft delete |
283
+ | `listUsers(inactive?)` | `UserRecord[]` | List users |
284
+ | `generateToken(user)` | `string` | Issue JWT |
285
+ | `verifyToken(token)` | `TokenPayload \| null` | Verify JWT |
286
+ | `refreshToken(token)` | `string \| null` | Refresh JWT |
143
287
 
144
288
  ### ctx.user
145
289
 
146
- 由中间件自动从 `Authorization: Bearer` `token` cookie 解析。
290
+ Auto-resolved from `Authorization: Bearer` or `token` cookie.
147
291
 
148
292
  ```ts
149
293
  interface User {
150
294
  id: string
151
295
  name: string
152
296
  email: string
153
- role: string // 'user' | 'admin' | ...
297
+ role: string
154
298
  [key: string]: unknown
155
299
  }
156
300
  ```
@@ -159,19 +303,19 @@ interface User {
159
303
 
160
304
  ```ts
161
305
  app.get('/admin', requireRole('admin'), handler)
162
- // 未登录 → 401,角色不匹配 → 403
306
+ // No auth → 401, wrong role → 403
163
307
  ```
164
308
 
165
- ### 安全
309
+ ### Security
166
310
 
167
- - 密码:scrypt + 随机 32 字节盐
168
- - 令牌:HMAC SHA-2567 天过期
311
+ - Password: scrypt + 32-byte random salt
312
+ - Token: HMAC SHA-256, 7 day expiry
169
313
 
170
314
  ---
171
315
 
172
316
  ## messager
173
317
 
174
- 即时消息 + AI 对话交互层。单聊、群聊、消息持久化、WebSocket 实时推送。
318
+ Instant messaging + AI conversation layer. Direct/group chat, message persistence, WebSocket push.
175
319
 
176
320
  ```ts
177
321
  import { messager } from 'weifuwu'
@@ -180,7 +324,7 @@ app.use(postgres())
180
324
  app.use(user())
181
325
  app.use(messager())
182
326
 
183
- // WebSocket — 自动加入用户的所有会话
327
+ // WebSocket — auto-join all user conversations
184
328
  app.ws('/ws', {
185
329
  async open(ws, ctx) {
186
330
  for (const c of await ctx.messager.getConversations()) {
@@ -209,32 +353,32 @@ app.get('/api/conversations/:id/messages', async (req, ctx) => {
209
353
 
210
354
  | Method | Returns | Description |
211
355
  |--------|---------|-------------|
212
- | `createDirectConversation(userId)` | `Conversation` | 创建/复用私聊 |
213
- | `createGroupConversation(title, userIds)` | `Conversation` | 建群 |
214
- | `sendMessage(convId, body)` | `Message` | 发消息,自动广播到 `conversation:{id}` 房间 |
215
- | `getMessages(convId, opts?)` | `Message[]` | 游标分页 |
216
- | `editMessage(msgId, body)` | `Message \| null` | 编辑(24h 内) |
217
- | `deleteMessage(msgId)` | `boolean` | 软删除 |
218
- | `getConversations()` | `Conversation[]` | 会话列表(含未读、最后消息预览) |
219
- | `getConversation(id)` | `Conversation \| null` | 会话详情 |
220
- | `markRead(convId)` | `void` | 标记已读 |
221
- | `getUnreadCount()` | `{ total, byConversation }` | 未读统计 |
222
- | `addParticipants(convId, userIds)` | `void` | 加人 |
223
- | `removeParticipant(convId, userId?)` | `boolean` | 退出/踢出 |
356
+ | `createDirectConversation(userId)` | `Conversation` | Create/reuse DM |
357
+ | `createGroupConversation(title, userIds)` | `Conversation` | Create group |
358
+ | `sendMessage(convId, body)` | `Message` | Send, auto-broadcast to `conversation:{id}` room |
359
+ | `getMessages(convId, opts?)` | `Message[]` | Cursor pagination |
360
+ | `editMessage(msgId, body)` | `Message \| null` | Edit (24h window) |
361
+ | `deleteMessage(msgId)` | `boolean` | Soft delete |
362
+ | `getConversations()` | `Conversation[]` | List with unread + last message |
363
+ | `getConversation(id)` | `Conversation \| null` | Get detail |
364
+ | `markRead(convId)` | `void` | Mark as read |
365
+ | `getUnreadCount()` | `{ total, byConversation }` | Unread stats |
366
+ | `addParticipants(convId, userIds)` | `void` | Add members |
367
+ | `removeParticipant(convId, userId?)` | `boolean` | Leave / kick |
224
368
 
225
- ### 存储
369
+ ### Storage
226
370
 
227
- 3 张表:`conversations` / `participants` / `messages`。自动建表迁移。
371
+ 3 tables: `conversations` / `participants` / `messages`. Auto-migration.
228
372
 
229
- ### AI 对话
373
+ ### AI Conversations
230
374
 
231
- messager + agent 两个模块组合 = ChatGPT 基础架构。messager 管会话和推送,agent LLM 生成。
375
+ messager + agent = ChatGPT foundation. Messager handles sessions + push, agent handles LLM generation.
232
376
 
233
377
  ---
234
378
 
235
379
  ## kb
236
380
 
237
- RAG 知识库。文档导入自动分片 → DashScope embedding → pgvector 存储语义搜索。
381
+ RAG knowledge base. Import docs auto-chunk → DashScope embedding → pgvector storagesemantic search.
238
382
 
239
383
  ```ts
240
384
  import { kb } from 'weifuwu'
@@ -242,14 +386,14 @@ import { kb } from 'weifuwu'
242
386
  app.use(postgres())
243
387
  app.use(kb())
244
388
 
245
- // 导入
389
+ // Import
246
390
  app.post('/api/kb/import', async (req, ctx) => {
247
391
  const { title, content } = await req.json()
248
392
  const result = await ctx.kb.importText(title, content)
249
393
  return Response.json(result, { status: 201 })
250
394
  })
251
395
 
252
- // 搜索
396
+ // Search
253
397
  app.post('/api/kb/search', async (req, ctx) => {
254
398
  const { query } = await req.json()
255
399
  return Response.json(await ctx.kb.search(query, { limit: 5 }))
@@ -260,30 +404,30 @@ app.post('/api/kb/search', async (req, ctx) => {
260
404
 
261
405
  | Method | Returns | Description |
262
406
  |--------|---------|-------------|
263
- | `importText(title, text, opts?)` | `{ document, chunks }` | 导入分片embedding存储 |
264
- | `importDocuments(docs)` | `Document[]` | 批量导入 |
265
- | `search(query, opts?)` | `SearchResult[]` | 语义搜索(cosine 相似度) |
266
- | `list()` | `Document[]` | 文档列表 |
267
- | `get(id)` | `Document \| null` | 文档详情 |
268
- | `getChunks(documentId)` | `Chunk[]` | 分片列表 |
269
- | `delete(id)` | `boolean` | 删除文档 + 级联删除分片 |
407
+ | `importText(title, text, opts?)` | `{ document, chunks }` | Importchunkembedstore |
408
+ | `importDocuments(docs)` | `Document[]` | Batch import |
409
+ | `search(query, opts?)` | `SearchResult[]` | Semantic search (cosine) |
410
+ | `list()` | `Document[]` | List documents |
411
+ | `get(id)` | `Document \| null` | Get document |
412
+ | `getChunks(documentId)` | `Chunk[]` | Get chunks |
413
+ | `delete(id)` | `boolean` | Delete + cascade chunks |
270
414
 
271
- ### 配置
415
+ ### Configuration
272
416
 
273
417
  ```ts
274
- // 默认:DashScope text-embedding-v4(环境变量 DASHSCOPE_API_KEY
418
+ // Default: DashScope text-embedding-v4 (env: DASHSCOPE_API_KEY)
275
419
  app.use(kb())
276
420
 
277
- // 自定义 embedding
421
+ // Custom embedding
278
422
  app.use(kb({
279
- embed: async (text) => { /* 返回 number[] */ },
423
+ embed: async (text) => { /* return number[] */ },
280
424
  dimensions: 1536,
281
- chunkSize: 512, // 默认分片大小(tokens
282
- chunkOverlap: 64, // 分片重叠
425
+ chunkSize: 512, // tokens
426
+ chunkOverlap: 64,
283
427
  }))
284
428
  ```
285
429
 
286
- ### Agent 配合
430
+ ### Integration with Agent
287
431
 
288
432
  ```ts
289
433
  app.use(agent({
@@ -294,16 +438,16 @@ app.use(agent({
294
438
  }))
295
439
  ```
296
440
 
297
- ### 存储
441
+ ### Storage
298
442
 
299
- - `kb_documents` — 原始文档元数据
300
- - `kb_chunks` — 分片内容 + VECTOR(1536) + TSVECTOR GIN 索引
443
+ - `kb_documents` — document metadata
444
+ - `kb_chunks` — chunk content + VECTOR(1536) + TSVECTOR GIN index
301
445
 
302
446
  ---
303
447
 
304
448
  ## agent
305
449
 
306
- AI Agent — LLM 对话、工具调用、RAG、流式输出。
450
+ AI Agent — LLM chat, tool calling, RAG, streaming.
307
451
 
308
452
  ```ts
309
453
  import { agent } from 'weifuwu'
@@ -327,13 +471,13 @@ app.use(agent({
327
471
  maxSteps: 5,
328
472
  }))
329
473
 
330
- // 流式对话
474
+ // Streaming
331
475
  app.post('/api/chat', async (req, ctx) => {
332
476
  const { messages } = await req.json()
333
477
  return ctx.agent.chatStreamResponse({ messages })
334
478
  })
335
479
 
336
- // 非流式对话
480
+ // Non-streaming
337
481
  app.post('/api/chat/sync', async (req, ctx) => {
338
482
  const { prompt } = await req.json()
339
483
  const text = await ctx.agent.chat(prompt)
@@ -344,31 +488,31 @@ app.post('/api/chat/sync', async (req, ctx) => {
344
488
  ### ctx.agent API
345
489
 
346
490
  | Method | Description |
347
- |--------|-------------|
348
- | `chat(prompt, opts?)` | 非流式对话,返回文本 |
349
- | `chatStreamResponse({ messages })` | SSE 流式回复(兼容 `useChat`) |
491
+ |-------------|-------------|
492
+ | `chat(prompt, opts?)` | Non-streaming, returns text |
493
+ | `chatStreamResponse({ messages })` | SSE stream (compatible with `useChat`) |
350
494
 
351
- ### 默认模型
495
+ ### Default Model
352
496
 
353
- - LLM: DeepSeek-V4-Flash(`@ai-sdk/openai` + `baseURL: 'https://api.deepseek.com/v1'`)
354
- - 环境变量 `DEEPSEEK_MODEL` 可覆盖模型名称
355
- - 环境变量 `DEEPSEEK_API_KEY` `OPENAI_API_KEY` 为 API 密钥
497
+ - LLM: DeepSeek-V4-Flash (via `@ai-sdk/openai` + `baseURL: 'https://api.deepseek.com/v1'`)
498
+ - Override with `DEEPSEEK_MODEL` env
499
+ - API key via `DEEPSEEK_API_KEY` or `OPENAI_API_KEY` env
356
500
 
357
501
  ### Features
358
502
 
359
503
  | Feature | Description |
360
504
  |---------|-------------|
361
- | `knowledge.search` | RAG 回调,自动注入 system prompt |
362
- | `tools` | 工具定义,自动循环调用(maxSteps |
363
- | `sandbox: true` | `ctx.sandbox` 集成(文件读写) |
364
- | `store` | 对话持久化(save/load |
365
- | `agents` | Agent 编排 |
505
+ | `knowledge.search` | RAG callback, auto-injected into system prompt |
506
+ | `tools` | Tool definitions, auto-loop (maxSteps) |
507
+ | `sandbox: true` | Integrates with `ctx.sandbox` |
508
+ | `store` | Session persistence (save/load) |
509
+ | `agents` | Multi-agent orchestration |
366
510
 
367
511
  ---
368
512
 
369
513
  ## cms
370
514
 
371
- 内容管理博客、文档、公告、更新日志。
515
+ Content management blog, docs, changelog.
372
516
 
373
517
  ```ts
374
518
  import { cms, requireRole } from 'weifuwu'
@@ -377,7 +521,7 @@ app.use(postgres())
377
521
  app.use(user())
378
522
  app.use(cms())
379
523
 
380
- // 公开:已发布的文章
524
+ // Public
381
525
  app.get('/api/posts', async (req, ctx) => {
382
526
  return Response.json(await ctx.cms.list({ type: 'post', status: 'published' }))
383
527
  })
@@ -387,7 +531,7 @@ app.get('/api/posts/:slug', async (req, ctx) => {
387
531
  return Response.json(post)
388
532
  })
389
533
 
390
- // 管理端
534
+ // Admin
391
535
  app.post('/api/admin/posts', requireRole('admin'), async (req, ctx) => {
392
536
  const post = await ctx.cms.create(await req.json())
393
537
  return Response.json(post, { status: 201 })
@@ -403,31 +547,31 @@ app.patch('/api/admin/posts/:id', requireRole('admin'), async (req, ctx) => {
403
547
 
404
548
  | Method | Returns | Description |
405
549
  |--------|---------|-------------|
406
- | `create(input)` | `Content` | 创建(admin |
407
- | `get(slug)` | `Content \| null` | slug 获取 |
408
- | `getById(id)` | `Content \| null` | id 获取 |
409
- | `update(id, input)` | `Content \| null` | 更新(admin |
410
- | `delete(id)` | `boolean` | 删除(admin |
411
- | `list(opts?)` | `Content[]` | 列表(游标分页) |
412
- | `publish(id)` | `Content \| null` | 发布(admin |
413
- | `unpublish(id)` | `Content \| null` | 下线(admin |
414
- | `listTags()` | `TagWithCount[]` | 标签列表 |
415
- | `createTag(name)` | `Tag` | 创建标签 |
550
+ | `create(input)` | `Content` | Create (admin) |
551
+ | `get(slug)` | `Content \| null` | Get by slug |
552
+ | `getById(id)` | `Content \| null` | Get by ID |
553
+ | `update(id, input)` | `Content \| null` | Update (admin) |
554
+ | `delete(id)` | `boolean` | Delete (admin) |
555
+ | `list(opts?)` | `Content[]` | List with cursor |
556
+ | `publish(id)` | `Content \| null` | Publish (admin) |
557
+ | `unpublish(id)` | `Content \| null` | Unpublish (admin) |
558
+ | `listTags()` | `TagWithCount[]` | List tags |
559
+ | `createTag(name)` | `Tag` | Create tag |
416
560
 
417
561
  ### Features
418
562
 
419
- - 内容类型:post / page / doc / changelog(任意字符串)
420
- - 状态:draft / published / archived
421
- - Slug:自动生成,同类型唯一
422
- - 标签:多对多,自动创建
423
- - 树形:parent_id 支持层级
424
- - 鉴权:非管理员只能读已发布
563
+ - Types: post / page / doc / changelog (any string)
564
+ - Status: draft / published / archived
565
+ - Slug: auto-generated, unique per type
566
+ - Tags: many-to-many, auto-created
567
+ - Tree: parent_id for hierarchy
568
+ - Auth: non-admin users see published only
425
569
 
426
570
  ---
427
571
 
428
572
  ## base
429
573
 
430
- 动态数据存储引擎让用户自定义数据结构(类似 Airtable)。
574
+ Dynamic data storage engine let users define their own data structures (like Airtable).
431
575
 
432
576
  ```ts
433
577
  import { base } from 'weifuwu'
@@ -436,13 +580,13 @@ app.use(postgres())
436
580
  app.use(user())
437
581
  app.use(base())
438
582
 
439
- // 定义数据结构
583
+ // Define schema
440
584
  app.post('/api/bases', async (req, ctx) => {
441
585
  const b = await ctx.base.create(await req.json())
442
586
  return Response.json(b, { status: 201 })
443
587
  })
444
588
 
445
- // 增删改查
589
+ // CRUD
446
590
  app.post('/api/bases/:id/:table', async (req, ctx) => {
447
591
  const row = await ctx.base.insert(ctx.params.id, ctx.params.table, await req.json())
448
592
  return Response.json(row, { status: 201 })
@@ -461,35 +605,37 @@ app.get('/api/bases/:id/:table', async (req, ctx) => {
461
605
 
462
606
  | Method | Returns | Description |
463
607
  |--------|---------|-------------|
464
- | `create({ name, tables })` | `BaseDef` | 创建数据库 |
465
- | `defineTable(baseId, schema)` | `BaseDef` | 加表 |
466
- | `updateTable(baseId, name, schema)` | `BaseDef \| null` | 改表 |
467
- | `removeTable(baseId, name)` | `BaseDef \| null` | 删表 |
468
- | `insert(baseId, table, data)` | `Row` | 插入行 |
469
- | `getRow(baseId, table, id)` | `Row \| null` | 查行 |
470
- | `updateRow(baseId, table, id, data)` | `Row \| null` | 改行 |
471
- | `deleteRow(baseId, table, id)` | `boolean` | 删行 |
472
- | `query(baseId, table, opts?)` | `Row[]` | 查询(filter/sort/limit/offset |
473
- | `search(baseId, table, field, query)` | `Row[]` | 全文搜索 |
474
- | `similaritySearch(baseId, table, field, vector)` | `Row[]` | 向量搜索 |
475
- | `list()` / `get(id)` / `getBySlug(slug)` / `delete(id)` | — | 数据库管理 |
476
-
477
- ### 架构
478
-
479
- Fixed Slot:一张 `base_data` 表,预分配 ~120 个物理列:
480
-
481
- | 类型 | 列数 | PG 类型 |
482
- |------|:----:|:--------:|
608
+ | `create({ name, tables })` | `BaseDef` | Create database |
609
+ | `defineTable(baseId, schema)` | `BaseDef` | Add table |
610
+ | `updateTable(baseId, name, schema)` | `BaseDef \| null` | Update table |
611
+ | `removeTable(baseId, name)` | `BaseDef \| null` | Remove table |
612
+ | `insert(baseId, table, data)` | `Row` | Insert row |
613
+ | `getRow(baseId, table, id)` | `Row \| null` | Get row |
614
+ | `updateRow(baseId, table, id, data)` | `Row \| null` | Update row |
615
+ | `deleteRow(baseId, table, id)` | `boolean` | Delete row |
616
+ | `query(baseId, table, opts?)` | `Row[]` | Query (filter/sort/limit/offset) |
617
+ | `search(baseId, table, field, query)` | `Row[]` | Full-text search |
618
+ | `similaritySearch(baseId, table, field, vector)` | `Row[]` | Vector search |
619
+ | `list()` / `get(id)` / `getBySlug(slug)` / `delete(id)` | — | Manage databases |
620
+
621
+ ### Architecture
622
+
623
+ Fixed Slot: a single `base_data` table with ~120 physical columns:
624
+
625
+ | Type | Count | PG Type |
626
+ |------|:-----:|:--------:|
483
627
  | text001..064 | 64 | TEXT |
484
628
  | number001..032 | 32 | DOUBLE PRECISION |
485
629
  | date001..008 | 8 | TIMESTAMPTZ |
486
630
  | vector001..004 | 4 | VECTOR(1536) |
487
631
  | search001..004 | 4 | TEXT |
488
- | ext | 1 | JSONB(溢出兜底) |
632
+ | ext | 1 | JSONB (overflow) |
489
633
 
490
- 字段名物理列号 的映射存储在 `base_column_map` 表。超出物理列的字段自动溢出到 ext JSONB
634
+ Field name physical column mapping stored in `base_column_map`. Fields beyond the physical columns overflow to ext JSONB.
491
635
 
492
- pgvector 自动检测:docker 镜像默认支持。
636
+ pgvector auto-detected (included in docker image).
637
+
638
+ ---
493
639
 
494
640
  ---
495
641
 
@@ -507,12 +653,12 @@ app.all(path, ...handlers)
507
653
  app.ws(path, ...middlewares, handler)
508
654
  // handler: { open?, message?, close?, error? }
509
655
 
510
- // 中间件 & 挂载
656
+ // Middleware & mounting
511
657
  app.use(middleware)
512
658
  app.mount(prefix, router)
513
659
  app.plugin(fn)
514
660
  app.onError(handler)
515
- app.routes() // 调试:列出所有路由
661
+ app.routes() // debug: list all routes
516
662
  ```
517
663
 
518
664
  ---
@@ -546,7 +692,7 @@ await sql.sql`SELECT * FROM users WHERE id = ${id}`
546
692
  await sql.sql.begin(async (sql) => { /* transaction */ })
547
693
  ```
548
694
 
549
- `DATABASE_URL` 环境变量。支持迁移、事务、连接池统计。
695
+ Reads `DATABASE_URL` env. Supports migrations, transactions, connection pool stats.
550
696
 
551
697
  ---
552
698
 
@@ -560,7 +706,7 @@ app.use(r) // → ctx.redis
560
706
  await r.redis.set('key', 'value')
561
707
  ```
562
708
 
563
- `REDIS_URL` 环境变量,默认 `redis://localhost:6379`。
709
+ Reads `REDIS_URL` env (default: `redis://localhost:6379`).
564
710
 
565
711
  ---
566
712
 
@@ -585,24 +731,36 @@ q.run()
585
731
 
586
732
  ```
587
733
  src/
588
- ├── index.ts ← 入口,导出所有模块
589
- ├── types.ts ← Context, Handler, Middleware 定义
734
+ ├── index.ts ← Entry, exports all modules
735
+ ├── types.ts ← Context, Handler, Middleware types
590
736
  ├── core/ ← serve, router, ws, trace, logger
591
737
  ├── middleware/ ← cors, helmet, compress, rate-limit, upload, static, sandbox
592
- ├── user/ ← 用户系统(CRUDJWTrequireRole
593
- ├── messager/ ← 即时消息 + AI 对话交互层
594
- ├── kb/ ← RAG 知识库(分片、embedding、向量搜索)
595
- ├── ai/ ← AI AgentLLMtoolsRAG
596
- ├── cms/ ← 内容管理(博客、文档、公告)
597
- ├── base/ ← 动态数据存储引擎(Fixed Slot
598
- ├── postgres/ ← PostgreSQL 客户端
599
- ├── redis/ ← Redis 客户端
600
- ├── queue/ ← 任务队列 + cron
601
- ├── react/ ← React SSR
738
+ ├── user/ ← User system (CRUD, JWT, requireRole)
739
+ ├── messager/ ← IM + AI conversation layer
740
+ ├── kb/ ← RAG knowledge base (chunking, embedding, vector search)
741
+ ├── ai/ ← AI Agent (LLM, tools, RAG)
742
+ ├── cms/ ← Content management (blog, docs, changelog)
743
+ ├── base/ ← Dynamic data engine (Fixed Slot)
744
+ ├── postgres/ ← PostgreSQL client
745
+ ├── redis/ ← Redis client
746
+ ├── queue/ ← Job queue + cron
602
747
  ├── graphql.ts ← GraphQL
603
748
  ├── hub.ts ← WebSocket hub
749
+ ├── client/ ← Frontend framework (signal, JSX, router)
750
+ │ ├── index.ts
751
+ │ ├── signal.ts
752
+ │ ├── jsx-runtime.ts
753
+ │ ├── app.ts
754
+ │ ├── router.ts
755
+ │ └── types.ts
604
756
  └── test/ ← 281 tests
605
757
 
758
+ apps/demo/ ← Full-stack demo
759
+ ├── src/main.tsx
760
+ ├── public/index.html
761
+ ├── tsconfig.json
762
+ └── scripts/build.mjs
763
+
606
764
  docker-compose.yml ← postgres (pgvector) + redis
607
765
  ```
608
766
 
@@ -611,7 +769,7 @@ docker-compose.yml ← postgres (pgvector) + redis
611
769
  ## Development
612
770
 
613
771
  ```bash
614
- docker compose up -d # 启动 postgres + redis
772
+ docker compose up -d # Start postgres + redis
615
773
  npm run build # esbuild → dist/
616
774
  npm run typecheck # tsc --noEmit
617
775
  npm test # 281 tests