weifuwu 0.91.0 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/server.md CHANGED
@@ -5,29 +5,183 @@
5
5
 
6
6
  ## 目录
7
7
 
8
+ - [0. API 速查](#0-api-速查)
8
9
  - [1. 快速上手](#1-快速上手)
9
10
  - [2. 中间件清单](#2-中间件清单)
10
11
  - [3. 环境变量](#3-环境变量)
11
12
  - [4. AI Stream Protocol](#4-ai-stream-protocol)
12
- - [5. 数据层](#5-数据层)
13
+ - [5. 数据层](#5-数据层)(含 [5.5 错误/响应面](#55-错误响应面api-计划-w0链落地后的-handler-最小形态))
13
14
  - [6. 实时与渲染](#6-实时与渲染)
14
15
  - [7. workflow 执行引擎](#7-workflow-执行引擎)
15
16
 
16
17
  ---
17
18
 
18
- ## 1. 快速上手
19
+ ## 0. API 速查
20
+
21
+ > 服务端主导出(`import { ... } from 'weifuwu'`)——按域速查。签名是
22
+ > 类型话的速记——以源码类型定义为准(`src/server/index.ts`)。
23
+
24
+ ### 0.1 HTTP 层
25
+
26
+ ```ts
27
+ serve(app, opts?) // 启动——app: Router——Web 标准 Request/Response
28
+ Router<T>() // 自研 Trie 路由——get/post/put/delete/patch/mount/use/onError
29
+ HttpError(msg, status) // 错误面——status 权威——链捕获 { error }
30
+ createMiddleware(fn) // 中间件声明(injects/depends 依赖注入)
31
+ cors()/compress()/serveStatic(dir)/rateLimit(opts) // 内置中间件
32
+ parseBody(req) // 通用 JSON 解析(bodyOf 是 orm shape 面——勿混)
33
+ ```
34
+
35
+ ```ts
36
+ const app = new Router().get('/', () => Response.json({ ok: true }))
37
+ serve(app, { port: 3000 })
38
+ ```
39
+
40
+ ### 0.2 响应面(response.ts 家族——单源)
41
+
42
+ ```ts
43
+ ok(data, init?) · created · noContent · badRequest · unauthorized · forbidden
44
+ · notFound · conflict · unprocessable · tooManyRequests · serverError · redirect
45
+ errorResponse(e) // 总错误面:DbError→400/409(code) · HttpError→status · 意外→500
46
+ ```
47
+
48
+ ```ts
49
+ return ok({ user }) // 200
50
+ throw new HttpError('无权', 403) // 链捕获 → { error: '无权' }(无 code)
51
+ ```
52
+
53
+ ### 0.3 ORM 面(数据层——见 §5)
54
+
55
+ ```ts
56
+ z / shape / f // zod 别名 + shape 构造器(f.pk/f.req/f.now/dflt……)
57
+ createOrm(opts) // Orm 实例——tables()/ctxTable/query/checkConsistency
58
+ memoryAdapter()·postgresAdapter() // 双后端 adapter
59
+ bodyOf(req, shape, opts) // shape 输入面——{ variant: 'insert'|'patch', omit }——BodyOf/PatchOf
60
+ listQuery(url, shape, opts) // 列表查询——过滤白名单/排序/limit clamp(20/100)
61
+ diffConsistency(a, b) // checkConsistency 纯函数——表/列 diff——error/warn 两级
62
+ ops // 算子域:eq/ne/gt/gte/lt/lte/inArray/ilike/contains/startsWith/endsWith/eqCol/isNull/isNotNull/and/or/not
63
+ createTypedQuery(shape) // 跨表查询类型化(typed-query.ts)
64
+ buildQuery / createQueryBuilder // 表达式构建(AST 面)
65
+ compileSchemaDDL(module) // schema 模块 → DDL(DDL 单源)
66
+ ```
67
+
68
+ ```ts
69
+ // route 内:T = ctx.orm.ctxTable 注册表(tables(orm) 或 ctx.orm.ctxTable)
70
+ const T = tables(ctx.orm)
71
+ const body = await bodyOf(req, T.agents, { variant: 'insert', omit: ['app_id'] })
72
+ const { filter, limit } = listQuery(url, T.agents, { defaultLimit: 50 })
73
+ await ctx.orm.checkConsistency(SHAPES)
74
+ ```
75
+
76
+ ### 0.4 AI 面
77
+
78
+ ```ts
79
+ new OpenAi({ apiKey, ... }) // OpenAI 兼容——llm/embedding/image/video
80
+ new MemoryAi({ onChat, onEmbed, ... }) // 内存确定性(测试/离线)
81
+ createMemoryAi(opts) · MemoryAiServer // 协议替身(HTTP 面——respond 注入)
82
+ ```
83
+
84
+ ### 0.5 认证面
85
+
86
+ ```ts
87
+ userSystem(opts) // 注册/登录/token/角色/租户(ctx.auth/ctx.user)
88
+ appAuth(opts) // 应用隔离(appId 注入)
89
+ hashPassword(pw) · verifyPassword(pw, hash) · signToken(payload) · verifyToken(t)
90
+ generateRefreshToken()
91
+ BUILTIN_APP_ID // 框架内置 app id
92
+ ```
93
+
94
+ ### 0.6 实时/调度/消息
95
+
96
+ ```ts
97
+ messager(opts) // 部门消息(ctx.msg——房间广播 + Redis 跨进程)
98
+ queue(opts) // 任务队列(worker/retry/backoff)
99
+ scheduler(opts) // 任务调度(持久化/恢复)
100
+ workflowSystem(opts) // 工作流引擎(见 §7)
101
+ ui(opts) // SSR + JS/CSS 编译
102
+ postgres(opts) · redis(opts) // 数据中间件(PG v3/RESP2 自研协议)
103
+ ```
104
+
105
+ ### 0.7 内存替身族(测试/离线——诚实内存化矩阵)
106
+
107
+ ```ts
108
+ MemorySql(opts)·MemoryRedis(opts) // 数据内存实现
109
+ MemoryRedisServer·MemoryPostgresServer // 线协议替身(TCP——客户端零改)
110
+ createMemoryOrm(opts) · MemoryEmail · createMemoryEmail · MemoryEmailServer // 协议替身(HTTP——respond 注入)
111
+ ```
112
+
113
+ ### 0.8 schema 常量
114
+
115
+ ```ts
116
+ WEIFUWU_USER_SCHEMA · WEIFUWU_MESSAGER_SCHEMA · WEIFUWU_WORKFLOW_SCHEMA · MIGRATIONS_TABLE
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 1. 快速上手(4 段动线——每段逐字可跑)
122
+
123
+ > 命名纪律:**入口是 `serve(app, opts)`**——`createServer`/`ctx.json` 是已消亡
124
+ > 的旧 API 残留(2026 初 Router 直调时代);handler 一律返回 Web 标准
125
+ > `Response`(`Response.json(...)`)——零自定义响应面。
126
+ > 入口:`src/server/index.ts` · 路由内核:`src/server/core/`(与前端 UIRouter
127
+ > 共享 `src/shared/router/` trie/pipeline 五层单源)。
128
+
129
+ ### 1.1 段①:serve + Router(3 行——服务器跑起来)
130
+
131
+ ```ts
132
+ import { serve, Router } from 'weifuwu'
133
+ const app = new Router().get('/api/hello', () => Response.json({ ok: true }))
134
+ serve(app, { port: 3000 })
135
+ ```
136
+
137
+ - 中间件:`app.use(...)`(按注册序)· 错误面:`app.onError((e) => errorResponse(e))`
138
+ - 更多(cors/compress/rateLimit/email/userSystem/ai/graphql/postgres/...)见 §2
139
+
140
+ ### 1.2 段②:shape + bodyOf + CRUD(10 行——数据面跑起来)
19
141
 
20
142
  ```ts
21
- import { createServer, Router } from 'weifuwu'
143
+ import { postgres, Router, z, f, bodyOf } from 'weifuwu'
144
+
145
+ const pg = postgres({ memory: true }) // 真库:postgres({ url })
146
+ const NOTES = { id: f.pk(z.uuid()), title: f.req(z.string()), body: z.string().nullable() }
147
+ await pg.migrateModule('demo', { tables: [{ name: 'notes', columns: NOTES }] })
148
+ pg.orm.table('notes', NOTES) // 注册表预注册(route 内同实例)
22
149
 
23
- const server = createServer()
24
- server.route(Router()
25
- .get('/api/hello', (req, ctx) => ctx.json({ ok: true })))
26
- server.listen(3000)
150
+ app.post('/api/notes', async (req, ctx) => {
151
+ const body = await bodyOf(req, ctx.orm.table('notes'), { variant: 'insert' })
152
+ const [row] = await ctx.orm.table('notes').insert(body).returning('*').run()
153
+ return Response.json(row, { status: 201 })
154
+ })
27
155
  ```
28
156
 
29
- 入口:`src/server/index.ts` · 路由内核:`src/server/core/`(Router/serve/WS hub——
30
- 与前端 UIRouter 共享 `src/shared/router/` trie/pipeline 五层单源)。
157
+ - `bodyOf` = shape 输入面(校验/日期/auto 列省略——错误 400 `{ error, code:'validation' }`)
158
+ - 字段声明纪律(`f.req/f.pk` 必填 · `.nullable()` 可空 · `f.now`/`dflt` 默认)见 §5.2
159
+
160
+ ### 1.3 段③:前端页面组件(useAsyncData 8 行——页面跑起来)
161
+
162
+ ```tsx
163
+ import { h } from 'weifuwu/vdom'
164
+ const NotesPage: Component = (_p, ctx) => {
165
+ const [get] = ctx.ui.useAsyncData(fetchNotes, 'notes-page') // 唯一异步边界
166
+ return () => h('ul', {}, (get() ?? []).map((n) => h('li', {}, n.title)))
167
+ }
168
+ ```
169
+
170
+ - `useAsyncData`:并发合并/竞态取消/缓存保留/卸载自动退订——loading 态
171
+ `get() ?? []`——**SSR 首帧 = 加载态**(同步渲染——数据后到 hydrate 填充)
172
+ - `ctx.ui` 是 hooks 注入面(14 个——usePopup/useControlled/useExternal...)——
173
+ 组件契约(工厂同步/渲染纯)见 docs/client.md §5
174
+
175
+ ### 1.4 段④:契约测试 5 行(锁行为)
176
+
177
+ ```ts
178
+ import { test } from 'node:test'
179
+ const res = await app.handler()(req, { params: {}, query: {} } as never)
180
+ assert.equal(res.status, 201) // memory orm + handler 直调——零浏览器
181
+ ```
182
+
183
+ - 契约层口径:引擎决策层输出纯数据(命令流/状态码)——node:test 直跑零浏览器
184
+ - 三层测试动线(契约→场景→showcase)见 docs/client.md §3 与 AGENTS.md §2
31
185
 
32
186
  ## 2. 中间件清单
33
187
 
@@ -221,6 +375,28 @@ export const agents: ZodRawShape = { ... }
221
375
  ——BodyOf 判定失效——平台 44 处 dflt 已迁移框架 `f.dflt`);字面量默认值
222
376
  任意值域(jsonb 默认 `[]` 等——f.dflt 泛型不限)
223
377
 
378
+ ### 5.6 跨端类型共享(fullstack W0——RowOf 派生 + import type 零打包)
379
+
380
+ > 前端行类型从后端 SHAPES 单源派生——**类型共享(import)优于类型生成**
381
+ > (codegen 有同步/漂移——文件产物天然落后于源)。
382
+
383
+ ```ts
384
+ // 前端(应用 ui/lib/types.ts——import type 编译后零代码)
385
+ import type { RowOf } from 'weifuwu'
386
+ import type { SHAPES } from '../../src/db/shapes.ts'
387
+ export type Agent = RowOf<(typeof SHAPES)['agents']> & {
388
+ token_usage?: TokenUsage // 查询附加字段——交叉扩展(shape 无的面)
389
+ }
390
+ ```
391
+
392
+ - **纪律**:行类型 = RowOf 派生 + 附加交叉(查询增强)——字段零手写
393
+ (shape 新增列前端自动获得——漂移 bug 根除);派生严格化(req 列必填)
394
+ 是特征不是破坏(后端返回必有)
395
+ - **零打包**:import type 编译后移除(esbuild bundle 验证——后端关键词 0 处)
396
+ - **判负**:codegen(type 生成管线)不做——类型共享优于生成(同步面零漂移)
397
+
398
+ ---
399
+
224
400
  ## 6. 实时与渲染
225
401
 
226
402
  - **scheduler**:`src/server/middleware/scheduler.ts`——`ctx.schedule.cron/once` +
@@ -554,3 +730,47 @@ app.get('/api/agents', async (req: Request, ctx: AppCtx): Promise<Response> => {
554
730
  dist 引框架——改 src 后必须 build——本轮 3 次 build 教训)
555
731
  - **盲区教训**:契约层 enum 测试只测 filter(输入面)——输出序列化/字面量
556
732
  输入(GraphQL 规范:enum 不带引号)首见于平台试点——盲区先补测再信绿
733
+
734
+ ### 5.5 错误/响应面(api 计划 W0——链落地后的 handler 最小形态)
735
+
736
+ > **链路**:route 抛错 → Router 链捕获(onError 自定义优先)→ 默认链
737
+ > `errorResponse(e)` 单源——handler 只写业务,错误交给链。
738
+
739
+ **handler 最小形态**(参数→orm→响应——错误零处理):
740
+
741
+ ```ts
742
+ app.get('/api/agents/:id', async (req, ctx) => {
743
+ const agent = await ctx.orm.table('agents').find(params.id) // 抛错 = 链兜
744
+ if (!agent) throw new HttpError('Agent 不存在', 404) // 一行错误
745
+ return ok(agent)
746
+ })
747
+ ```
748
+
749
+ **错误码面**(`{ error, code }`——前端可 switch——不解析 message):
750
+
751
+ ```ts
752
+ // 后端(route 内不用 catch——链捕获统一)
753
+ throw new ValidationError('参数校验失败') // → 400 { error, code: 'validation' }
754
+ // orm 冲突(23505)自动 → 409 { error, code: 'conflict' }
755
+
756
+ // 前端
757
+ const res = await api.get('/api/agents')
758
+ catch (e) {
759
+ switch (e.code) { // 'validation' | 'conflict' | DbError.kind ...
760
+ case 'validation': return formErrors(e)
761
+ case 'conflict': return notify('已存在')
762
+ }
763
+ }
764
+ ```
765
+
766
+ **双层语义(有意分层——不是遗漏)**:
767
+
768
+ | 错误源 | 链面(未捕获) | route 内 catch(已知业务) |
769
+ | --- | --- | --- |
770
+ | 普通 Error | 500 `{ error: 'Internal Server Error' }`(意外诚实现形——消息不泄漏) | 400 `{ error }`(已知业务显式化) |
771
+ | HttpError | status 权威(400/401/403/404/409...) | 同(双面一致) |
772
+ | DbError/ValidationError | 400/409 + code(orm 错误不该 500) | 同 |
773
+
774
+ **判负**:响应信封统一(`{ data, total }` 包装所有 200)不做——裸 JSON 保持
775
+ (信封是审美不是需求——架构成本 > 收益);前端解析规则:200 = 业务数据本体;
776
+ 4xx/5xx = `{ error, code? }`。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.91.0",
4
+ "version": "0.92.0",
5
5
  "description": "AI SaaS full-stack framework — backend HTTP + frontend VDOM + 129 components + CSS design system + SaaS foundation (auth/queue/AI). Zero-build, zero-dependency.",
6
6
  "exports": {
7
7
  ".": {
@@ -52,15 +52,17 @@
52
52
  "test:components": "node --env-file=.env --test --test-timeout=8000 --test-concurrency=2 src/test/scenario/e2e-6.test.ts",
53
53
  "audit:semantics": "node scripts/audit-core-semantics.mjs",
54
54
  "audit:showcase": "node scripts/audit-showcase-dev.mjs",
55
- "audit:all": "npm run audit:semantics && npm run audit:interactivity && npm run audit:vdom && npm run audit:theme && npm run audit:api && npm run audit:bundle && npm run audit:showcase",
55
+ "audit:all": "npm run audit:semantics && npm run audit:interactivity && npm run audit:vdom && npm run audit:theme && npm run audit:api && npm run audit:bundle && npm run audit:showcase && npm run audit:docs",
56
56
  "test:contract-components": "node --env-file=.env --test 'src/client/components/**/*.test.ts'",
57
57
  "audit:interactivity": "node scripts/audit-interactivity.mjs",
58
58
  "audit:theme": "node scripts/audit-theme.mjs",
59
59
  "audit:api": "node scripts/audit-api.mjs",
60
60
  "audit:bundle": "node scripts/audit-bundle.mjs",
61
61
  "audit:vdom": "node scripts/audit-vdom.mjs",
62
+ "audit:docs": "node scripts/audit-docs-api.mjs",
62
63
  "typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
63
- "test:perf": "PERF_BASELINE=1 node --env-file=.env --test src/server/core/router-contract.test.ts"
64
+ "test:perf": "PERF_BASELINE=1 node --env-file=.env --test src/server/core/router-contract.test.ts",
65
+ "test:format": "node --test scripts/changelog-format.test.mjs"
64
66
  },
65
67
  "dependencies": {
66
68
  "esbuild": "^0.28.1",