weifuwu 0.90.0 → 0.91.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/README.md +4 -2
- package/dist/client/components/WordCloud/WordCloud.d.ts +79 -0
- package/dist/client/components/index.d.ts +2 -0
- package/dist/client/components/index.js +33 -33
- package/dist/client/components/style.css +95 -1
- package/dist/client/layout/weifuwu-layout.css +19 -0
- package/dist/client/vdom/core/v2/diff.d.ts +2 -2
- package/dist/client/vdom/hooks/env.d.ts +20 -0
- package/dist/client/vdom/index.js +3 -3
- package/dist/client/vdom/testing.js +2 -2
- package/dist/server/ai/approvals.d.ts +24 -0
- package/dist/server/ai/assemble.d.ts +19 -0
- package/dist/server/ai/client.d.ts +22 -0
- package/dist/server/ai/contracts.d.ts +67 -2
- package/dist/server/ai/index.d.ts +14 -43
- package/dist/server/ai/memory-server.d.ts +50 -0
- package/dist/server/ai/memory.d.ts +46 -0
- package/dist/server/ai/multimodal.d.ts +52 -0
- package/dist/server/ai/openai.d.ts +16 -0
- package/dist/server/db/body.d.ts +31 -0
- package/dist/server/db/consistency.d.ts +28 -0
- package/dist/server/db/contracts.d.ts +3 -27
- package/dist/server/db/filter.d.ts +24 -0
- package/dist/server/db/gql-from-shape.d.ts +42 -0
- package/dist/server/db/http.d.ts +38 -0
- package/dist/server/db/memory-sql.d.ts +66 -8
- package/dist/server/db/ops.d.ts +70 -0
- package/dist/server/db/orm.d.ts +110 -0
- package/dist/server/db/query-builder.d.ts +4 -2
- package/dist/server/db/query.d.ts +90 -32
- package/dist/server/db/rest-from-shape.d.ts +56 -0
- package/dist/server/db/schema.d.ts +78 -0
- package/dist/server/db/server.d.ts +0 -2
- package/dist/server/db/shape.d.ts +131 -0
- package/dist/server/db/typed-query.d.ts +113 -0
- package/dist/server/email/api.d.ts +15 -0
- package/dist/server/email/contracts.d.ts +8 -3
- package/dist/server/email/index.d.ts +23 -30
- package/dist/server/email/memory-email-server.d.ts +43 -0
- package/dist/server/email/memory-email.d.ts +30 -0
- package/dist/server/index.d.ts +33 -10
- package/dist/server/index.js +9843 -4133
- package/dist/server/messager/index.d.ts +133 -12
- package/dist/server/postgres/client.d.ts +4 -4
- package/dist/server/postgres/types.d.ts +33 -13
- package/dist/server/redis/types.d.ts +2 -0
- package/dist/server/response.d.ts +3 -3
- package/dist/server/types.d.ts +2 -5
- package/dist/server/user/app-auth.d.ts +74 -0
- package/dist/server/user/index.d.ts +277 -5
- package/dist/server/workflow/contracts.d.ts +202 -0
- package/dist/server/workflow/expression.d.ts +89 -0
- package/dist/server/workflow/index.d.ts +70 -0
- package/dist/server/workflow/index.js +2476 -0
- package/dist/server/workflow/runner.d.ts +19 -0
- package/dist/server/workflow/std.d.ts +27 -0
- package/dist/server/workflow/steps.d.ts +12 -0
- package/dist/server/workflow/store.d.ts +19 -0
- package/dist/server/workflow/tojs.d.ts +28 -0
- package/dist/server/workflow/validate.d.ts +23 -0
- package/dist/server/workflow/views.d.ts +66 -0
- package/dist/server/workflow/wfjs.d.ts +27 -0
- package/dist/server/workflows/cron.d.ts +16 -0
- package/dist/server/workflows/index.d.ts +267 -0
- package/dist/shared/zod.d.ts +351 -0
- package/docs/client.md +6 -0
- package/docs/server.md +455 -11
- package/package.json +9 -3
- package/dist/server/db/postgres-server.d.ts +0 -39
- package/dist/server/db/sql-parser.d.ts +0 -24
- package/dist/server/db/test-servers.d.ts +0 -21
- package/dist/server/email/smtp.d.ts +0 -34
package/docs/server.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- [4. AI Stream Protocol](#4-ai-stream-protocol)
|
|
12
12
|
- [5. 数据层](#5-数据层)
|
|
13
13
|
- [6. 实时与渲染](#6-实时与渲染)
|
|
14
|
+
- [7. workflow 执行引擎](#7-workflow-执行引擎)
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
@@ -34,15 +35,15 @@ server.listen(3000)
|
|
|
34
35
|
|
|
35
36
|
| 中间件 | ctx 面 | 说明 |
|
|
36
37
|
| --- | --- | --- |
|
|
37
|
-
| `postgres()` | `ctx.
|
|
38
|
+
| `postgres()` | `ctx.orm` | PG v3 自研协议——ORM AST 面(业务零 SQL 文本——见 §5 数据层) |
|
|
38
39
|
| `redis()` | `ctx.redis` | RESP2 自研协议 |
|
|
39
40
|
| `scheduler()` | `ctx.schedule` | 任务调度(持久化/恢复) |
|
|
40
41
|
| `queue()` | `ctx.queue` | 任务队列(worker/retry/backoff) |
|
|
41
42
|
| `messager()` | `ctx.msg` | 部门内部消息(面向 agent) |
|
|
42
43
|
| `userSystem()` | `ctx.auth`/`ctx.user` | 注册/登录/token/角色/租户 |
|
|
43
44
|
| `rateLimit()` | — | 令牌桶(per-key/per-user) |
|
|
44
|
-
| `email()` | `ctx.email` |
|
|
45
|
-
| `
|
|
45
|
+
| `email()` / `MemoryEmail()` | `ctx.email` | 邮件接口(HTTP API 发送——Resend 兼容端点——不直连 SMTP);`new MemoryEmail({ onSend })` 内存确定性(测试/离线) |
|
|
46
|
+
| `OpenAi()` / `MemoryAi()` | `ctx.ai` | AI 接口(AIInterface——LLM/embedding/多模态);正门构造:`new OpenAi({ apiKey })`(OpenAI 兼容)/ `new MemoryAi({ onChat })`(确定性内存——测试/离线) |
|
|
46
47
|
| `ui()` | `ctx.ui` | SSR + JS/CSS 编译(`html/js/css/ssr`) |
|
|
47
48
|
| `graphql()` | `ctx.gql` | GraphQL 层(Schema/Resolver) |
|
|
48
49
|
| `ws()` | `ctx.ws` | WebSocket hub(订阅/广播) |
|
|
@@ -62,10 +63,41 @@ server.listen(3000)
|
|
|
62
63
|
| `AUTH_SECRET` | userSystem HMAC 密钥(≥16 字符) | `userSystem()` | — |
|
|
63
64
|
| `DEEPSEEK_API_KEY` | LLM provider key | `ai()` | — |
|
|
64
65
|
| `DEEPSEEK_BASE_URL` / `MODEL` | LLM 端点/模型 | `ai()` | `api.deepseek.com/v1` / `deepseek-v4-flash` |
|
|
65
|
-
| `DASHSCOPE_*` | embedding provider | `ai({ embedding })` | `deepseek` 同域 |
|
|
66
|
+
| `DASHSCOPE_*` | embedding + 多模态 provider(图片/视频) | `ai({ embedding })` / `ai()` | `deepseek` 同域 / `dashscope.aliyuncs.com` |
|
|
66
67
|
| `RESEND_API_KEY` / `SMTP_*` | 邮件 adapter | `email()` | `localhost:3025` |
|
|
67
68
|
| `PORT` | 服务端口 | `serve()` | `3000` |
|
|
68
69
|
|
|
70
|
+
## 3.5 AI 接口(AIInterface——provider 可插拔)
|
|
71
|
+
|
|
72
|
+
参考 PostgresInterface 分层(契约/工厂/引擎分离——`src/server/ai/`):
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
contracts.ts AIInterface(契约——Ai 兼容别名)+ 多模态请求/响应类型 + Context.ai 声明单源
|
|
76
|
+
client.ts OpenAI 兼容 transport(chat/stream/agent 引擎依赖面 = AiClient)
|
|
77
|
+
multimodal.ts DashScope 多模态(图片 z-image-turbo / 视频 happyhorse 异步任务——provider 语义)
|
|
78
|
+
memory.ts MemoryAi(参考 MemorySql:契约直实现——确定性——onChat/onEmbed/onImage 注入)
|
|
79
|
+
memory-server.ts MemoryAiServer(参考 MemoryPostgresServer——协议替身——测试用)
|
|
80
|
+
index.ts 模块 re-export(OpenAi/MemoryAi/AiClientModule——选择器已废——正门构造)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- **正门构造**:`new OpenAi(opts)` / `OpenAi(opts)`(env 读 DEEPSEEK_*——无 key 明确
|
|
84
|
+
throw)与 `new MemoryAi(opts)` / `createMemoryAi(opts)`(不读 DEEPSEEK_*——无 key
|
|
85
|
+
可用——测试/离线)——两者都返回 AiClientModule(`app.use` 注入 `ctx.ai`)
|
|
86
|
+
- **多供应商平级配置**:聊天走 `baseUrl/apiKey/defaultModel`;`embedding` / `image` /
|
|
87
|
+
`video` 各自独立选项(各自 `baseUrl/apiKey/model`——可指向不同端点/不同 key/不同
|
|
88
|
+
模型——如 chat=deepseek + embedding/image/video=dashscope)。默认模型:图片
|
|
89
|
+
`z-image-turbo`、视频 `happyhorse-1.1-t2v`(复用现状常量)
|
|
90
|
+
- **多模态**:`ctx.ai.generateImage / createVideoTask / videoStatus`——**只做 provider
|
|
91
|
+
语义**(不落盘/不建任务行/不轮询——编排属应用层);视频参数归一在 provider
|
|
92
|
+
层(枚举/时长夹紧/watermark)
|
|
93
|
+
- **MemoryAi**:`createMemoryAi({ onChat, onEmbed, onImage, onVideoSubmit, onVideoStatus })`——
|
|
94
|
+
默认 echo 末条 user 消息 / djb2 确定性向量 / 1×1 占位图 / 视频立即 done——
|
|
95
|
+
不编造 tool_calls(对齐 MemorySql「不支持的抛 unsupported」纪律)
|
|
96
|
+
- **MemoryAiServer**:`createMemoryAiServer({ port, onChat })`——OpenAI 兼容
|
|
97
|
+
(/v1/chat/completions 流+非流、/v1/embeddings)+ dashscope 格式(图片/视频)——
|
|
98
|
+
真实客户端(createAiClient / createDashscopeMultimodal)**零改动直连**——认证直过——
|
|
99
|
+
测试用(DEEPSEEK_BASE_URL 指向)
|
|
100
|
+
|
|
69
101
|
## 4. AI Stream Protocol
|
|
70
102
|
|
|
71
103
|
**协议即类型**(单源):`src/server/ai/types.ts`——修改协议先改此处,两侧实现对齐。
|
|
@@ -87,14 +119,107 @@ server.listen(3000)
|
|
|
87
119
|
|
|
88
120
|
消费端:`ctx.ai.chat()`(服务端流式)与 `useChat`(客户端会话语义层)。
|
|
89
121
|
|
|
90
|
-
## 5.
|
|
122
|
+
## 5. 数据层(协议层 = AST——零 SQL 文本面)
|
|
123
|
+
|
|
124
|
+
> **生效规则(2026-09 协议 AST 化收口)**:业务/测试**禁止** SQL 文本面——
|
|
125
|
+
> sql 模板 / unsafe / whereRaw / SQL parser 已全链消亡(W3 删净——audit-orm
|
|
126
|
+
> 双范围 0 防回流)。唯一数据入口 = **ORM AST 面**(Query 纯数据——可序列化、
|
|
127
|
+
> 可契约断言):
|
|
128
|
+
>
|
|
129
|
+
> ```ts
|
|
130
|
+
> // 查询(builder 构建 → AST 执行;协议传输 = toQuery() 纯数据)
|
|
131
|
+
> const rows = await ctx.orm.query.from('users')
|
|
132
|
+
> .where({ age: { gt: 18 } }).orderBy('created_at', 'desc').limit(10).run()
|
|
133
|
+
> // 写:insert(多行/returning/onConflict EXCLUDED)/ update / delete(必须带 where)
|
|
134
|
+
> await ctx.orm.query.insert('users').rows([{ email: 'a@b.c', role: 'member' }])
|
|
135
|
+
> .onConflict('email', true).returning('id').run()
|
|
136
|
+
> // 表绑定(shape 类型安全)与派生:ctx.orm.table('users', shape).select(...)
|
|
137
|
+
> // 测试播种/嵌入执行:orm.execute(query)(AST 直执行)+ createMemoryOrm()
|
|
138
|
+
> // (内存引擎——零数据库测试)
|
|
139
|
+
> ```
|
|
140
|
+
>
|
|
141
|
+
> 全局规则:写入必须表达式化(merge 值 RawSql 仅限 `__now`/`__interval`/`__inc`
|
|
142
|
+
> 等内置算子——`query.ts` compileMergeVal 单一编码面);DDL 走
|
|
143
|
+
> `migrateModule(name, SchemaModule)`(声明式——零 SQL 字符串);迁移面
|
|
144
|
+
> `runMigration(name, sql)` 是 DDL 唯一合法文本面(业务查询禁)。
|
|
91
145
|
|
|
92
|
-
- **postgres**:`src/server/
|
|
93
|
-
(无第三方客户端依赖)——`ctx.
|
|
94
|
-
|
|
95
|
-
- **
|
|
96
|
-
|
|
97
|
-
-
|
|
146
|
+
- **postgres**:`src/server/postgres/client.ts`——自研 PG v3 wire protocol
|
|
147
|
+
(无第三方客户端依赖)——`postgres()` 返回 PostgresClient:`ctx.orm` 注入
|
|
148
|
+
(ORM 唯一数据入口)+ migrate/migrateModule/runMigration/transaction
|
|
149
|
+
- **redis**:`src/server/db/memory-redis.ts` + `src/server/db/redis-server.ts`——
|
|
150
|
+
自研 RESP2——`ctx.redis.get/set/pub`(命令面本身封闭——无 parser——保留)
|
|
151
|
+
- **Query Language(协议层 = AST)**:`src/server/db/`——`query.ts`(Query 类型 + compileQuery
|
|
152
|
+
单向 SQL 编译——封闭输出)/ `query-builder.ts`(buildQuery——构建无执行面)/
|
|
153
|
+
`orm.ts`(shape+operator+adapter 组合体——table/gql/rest/tables() 注册表枚举)/
|
|
154
|
+
`memory-sql.ts`(MemorySql——AST 直执行 双后端同构)——`createMemoryOrm()` 零数据库跑测试
|
|
155
|
+
- **shape + 协议面(W0-W4 体验提升)**:`shape.ts`(shape 实体——变体
|
|
156
|
+
insertSchema/updateSchema(auto 列省略 · omit 系统列)· `BodyOf`/`PatchOf`
|
|
157
|
+
类型面 · `f` 元数据快捷(meta 类型保留))/ `body.ts`(`bodyOf`——shape →
|
|
158
|
+
body 校验)/ `http.ts`(`listQuery` + `errorResponse`——URL 参数与 catch 样板
|
|
159
|
+
收口)/ `consistency.ts`(`checkConsistency` 诊断 diff——真库/内存共用)/
|
|
160
|
+
`gql-from-shape.ts` + `rest-from-shape.ts`(协议生成面——§5.4)
|
|
161
|
+
- schema 迁移:`src/server/db/schema.ts`(SchemaModule → compileSchemaDdl 声明式 DDL)
|
|
162
|
+
+ `migrateModule` 执行记录(幂等——已迁移名跳过)
|
|
163
|
+
|
|
164
|
+
### 5.1 typedQuery——跨表查询类型化(2027-11 W3)
|
|
165
|
+
|
|
166
|
+
> **问题**:`orm.query.from(...)` 裸查询行类型恒为 `Row`(索引签名 unknown)——
|
|
167
|
+
> join/聚合查询的消费端被迫 `as unknown as X` 断言。**typedQuery 是纯类型面**
|
|
168
|
+
> (运行时 = SelectBuilder 链转发——零解析零成本)。
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { createTypedQuery } from 'weifuwu'
|
|
172
|
+
import { SHAPES } from '../db/shapes.ts' // 平台:表名 → shape 注册表
|
|
173
|
+
|
|
174
|
+
const Q = createTypedQuery(orm, { kb_chunks: SHAPES.kb_chunks, kb_documents: SHAPES.kb_documents })
|
|
175
|
+
|
|
176
|
+
const rows = await Q.from('kb_chunks kc') // from spec: 表名 + 可选 alias
|
|
177
|
+
.join('kb_documents kd', { 'kd.id': { col: 'kc.document_id' } }) // join alias 累积
|
|
178
|
+
.select('kc.id', 'kc.content', 'kd.filename') // 列引用:alias.col / 裸列=主表
|
|
179
|
+
.vectorScore('kc.embedding', vec, 'similarity') // pgvector 投影(AS 键并入行)
|
|
180
|
+
.where({ 'kc.agent_id': { eq: String(agentId) } }) // where 列白名单校验
|
|
181
|
+
.limit(5)
|
|
182
|
+
.run()
|
|
183
|
+
// rows[0].id: string · rows[0].filename: string | null · rows[0].similarity: number
|
|
184
|
+
// rows[0].nope → 编译期红(未知列);where({ 'zz.id': ... }) → 红(未注册 alias)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- 行类型规则:select 列键 = 去 alias 前缀(`kc.id` → `id`);aggregate/vectorScore
|
|
188
|
+
的 AS 键并入(number);同名列后覆盖(判负面:需要别名键时用单表查询/聚合 as)
|
|
189
|
+
- 编译期红线:未知列 · 未知 alias · where 非法列 —— tsd 测试见
|
|
190
|
+
`src/server/db/typed-query.test.ts`(typecheck:tests 守卫面)
|
|
191
|
+
- 判负(诚实裁剪):`col AS alias` 键别名不做(聚合 as 参数已覆盖);where 值
|
|
192
|
+
×列类型绑定不做(z.enum 字面量坍缩——见 §5.2)
|
|
193
|
+
|
|
194
|
+
### 5.2 shape 声明纪律(satisfies——字段字面量保留)
|
|
195
|
+
|
|
196
|
+
> **平台 shapes 唯一合法形态**:`export const agents = { ... } satisfies ZodRawShape`
|
|
197
|
+
> (**禁止** `: ZodRawShape` 注解——注解会让字段值坍缩为 ZodType<unknown> →
|
|
198
|
+
> `Infer` 全 unknown → tables() 行类型失效 → 消费端被迫 as unknown as)。
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
// ✓ satisfies(字段字面量保留——row.name: string · row.model: string | null)
|
|
202
|
+
export const agents = { id: f.pk(z.uuid()), name: f.req(z.string()) } satisfies ZodRawShape
|
|
203
|
+
// ✗ 注解(行类型坍缩——审计/tsc 面会持续捕杀)
|
|
204
|
+
export const agents: ZodRawShape = { ... }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- **行类型单源**:业务行接口(如 SandboxRow/VideoTaskRow)应从
|
|
208
|
+
`RowOf<typeof SHAPES.xxx>` 派生——**禁止手动双写**(shape 改列 → 行类型联动)
|
|
209
|
+
- **枚举单源**(W4):`z.enum(['ai','user'])` 字面量 tuple 推断(U 技巧——
|
|
210
|
+
`Infer` = 'ai'|'user'——`eq(type,'robot')` 编译期红);平台枚举值常量
|
|
211
|
+
(如 `AGENT_TYPES`)从 shape 声明派生 DDL enum(加枚举值只改一处)
|
|
212
|
+
- **vector 列**:`z.vector(1024)`(Infer=`number[]`——embedding 不再
|
|
213
|
+
ZodJson unknown)· 维度校验 · 内存/传输面按 json 数组承载;真库列型
|
|
214
|
+
`vector(dims)` 经 SchemaModule columnTypes 特化(shape 语义单源)
|
|
215
|
+
- **守卫面**:`tsconfig.test.json` + `npm run typecheck:tests`(测试域类型错
|
|
216
|
+
编译期拦截——tsd 断言真生效——负向验证:删 @ts-expect-error → 红)
|
|
217
|
+
- **meta 类型保留(W0 体验提升实证)**:`f.pk`/`f.now`/`f.dflt` 等元数据装饰
|
|
218
|
+
必须保留字面量类型(`withMeta` 交叉——meta 不坍缩)——`BodyOf<S>` 的
|
|
219
|
+
auto 列省略(default:'random'/'now')与 default 列可缺省判定从 meta
|
|
220
|
+
字面量推导——**禁止** `t.meta({ default: v }) as T`(`as T` 擦除 meta 类型
|
|
221
|
+
——BodyOf 判定失效——平台 44 处 dflt 已迁移框架 `f.dflt`);字面量默认值
|
|
222
|
+
任意值域(jsonb 默认 `[]` 等——f.dflt 泛型不限)
|
|
98
223
|
|
|
99
224
|
## 6. 实时与渲染
|
|
100
225
|
|
|
@@ -107,6 +232,325 @@ server.listen(3000)
|
|
|
107
232
|
|
|
108
233
|
---
|
|
109
234
|
|
|
235
|
+
## 7. workflow 执行引擎(含框架系统 workflowSystem)
|
|
236
|
+
|
|
237
|
+
> **框架系统**(对齐 messager/user 模式):`workflowSystem({ sql, redis })`——存储/编排层(`src/server/workflows/`):
|
|
238
|
+
>
|
|
239
|
+
> ```ts
|
|
240
|
+
> const wfs = workflowSystem({ orm: pg.orm, redis: redisClient?.redis })
|
|
241
|
+
> app.use(wfs) // 中间件:ctx.wf 注入(compileGate/execute/validate/dag/schema/defToWfjs)
|
|
242
|
+
> await wfs.migrate() // 幂等建表:_weifuwu_workflows(def_json 真相 + src_wfjs 视图)/ _weifuwu_workflow_runs
|
|
243
|
+
> wfs.routes(app) // 可选内置 API(缺省 /api/workflows + ctx.auth.appId——user 会话透传——默认即安全)
|
|
244
|
+
> ```
|
|
245
|
+
>
|
|
246
|
+
> - **路由参数默认化**:prefix 缺省 `/api/workflows`;appId 提取器缺省 `ctx.auth?.appId`(user() 中间件会话透传——非 user 会话才需自定义)
|
|
247
|
+
> - **compileGate**:wfjs → compileWfjs(v0 无 remoteFetch——远程导入编译错=出网安全线)→ validate(变量自足/步骤 id 存在性)→ 通过才入库——LLM 生成/用户编辑共用闸门
|
|
248
|
+
> - **执行**:`workflow({ store: redisStore })` 引擎注册制——run 落库(status/args/result/error/时间戳——长任务由 runs 承载)
|
|
249
|
+
> - **视图适配**:`ctx.wf.dag(def)`(Pipeline 数据——子链折叠标签)/ `ctx.wf.schema()`(JsonSchema——JsonSchemaForm 直消费)
|
|
250
|
+
> - 引擎(语言/执行/视图适配)= 下方纯能力包 `weifuwu/workflow`(零依赖导出——compileWfjs/toJs/toJsonSchema/workflowToDag)
|
|
251
|
+
|
|
252
|
+
引擎入口
|
|
253
|
+
|
|
254
|
+
`src/server/workflow/`——声明式执行引擎(WorkflowDef 线性步骤链 → ctx 数据流 → RunResult)。
|
|
255
|
+
入口:`workflow({ ai?, email?, redis?, fetch?, log? })`(模块即客户端,worker 直接调用)——
|
|
256
|
+
不做调度装配(cron/队列由消费方组合 scheduler/queue 实现)。
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { workflow } from 'weifuwu'
|
|
260
|
+
const wf = workflow({ ai: a, email: mail, redis: redisClient.redis }) // 适配器可选
|
|
261
|
+
|
|
262
|
+
const def = {
|
|
263
|
+
id: 'stock-monitor',
|
|
264
|
+
steps: [
|
|
265
|
+
{ id: 'probe', type: 'http', config: { url: 'https://api.test/stock' } },
|
|
266
|
+
{ id: 'gate', type: 'if', config: { when: 'steps.probe.data.json.items', edge: true } },
|
|
267
|
+
{ id: 'mail', type: 'email', config: { to: 'ops@x.com', subject: '预警', body: '{{steps.probe.data.json.items}}' } },
|
|
268
|
+
],
|
|
269
|
+
}
|
|
270
|
+
wf.validate(def) // → { ok, errors[] }(LLM 生成 / 配置共用闸门)
|
|
271
|
+
const r = await wf.execute(def) // → RunResult;execute(def, { mode: 'dry' }) 副作用打桩
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**语义红线**(`src/server/workflow/*.test.ts` 契约锁定——118 契约):
|
|
275
|
+
|
|
276
|
+
| 语义 | 定版 |
|
|
277
|
+
| --- | --- |
|
|
278
|
+
| 表达式 | JS 语义子集(手写递归下降——无 eval):`===`/`==`/`!==`/`!=`/比较/算术(严格防呆:`'1'+1` 抛错)/三元/`&&`/`||` 返回操作数/`[*]` 投影/`.length`/std 纯函数调用(需 import) |
|
|
279
|
+
| 布尔语境 | **JS truthy**:0/false/''/null/undefined → false;'0' 与 [] 为 true(JS 一致) |
|
|
280
|
+
| 比较 | 宽松 `==`/`!=` 保持 JS('200'==200);严格 `===`/`!==` 亦支持 |
|
|
281
|
+
| 短路 | 步骤级 `when` 不通过 → 跳过(skippedSteps)**后续继续**;`if` 分支条件 false → 跳过分支子链继续 |
|
|
282
|
+
| 去重 | **无原语**(once/edge 已删)——失败不记账、发送重试 at-least-once 由 store 显式记账:`store.get/set` 用户自查(库存监控首例) |
|
|
283
|
+
| dry-run | effects 步骤打桩 `{ok,dry}`;http 真跑(用户要看数据) |
|
|
284
|
+
| 每步输出 | `{ ok, data?, error? }` 落 `steps.<id>`(ctx.steps 与 RunResult.stepResults 同引用) |
|
|
285
|
+
| 内置步骤 | http / template / log / ai(需 ai 适配器)/ email(需 email 适配器)/ store(需 store/redis 注入)——缺适配器报明确错误,不静默跳过 |
|
|
286
|
+
|
|
287
|
+
**wfjs 是完整语言**(compileWfjs 编译 → WorkflowDef);`toJs(def)` 渲染回源码——round-trip 恒等(fuzz 500 对):
|
|
288
|
+
|
|
289
|
+
- 控制流:`if/else`、`while`、`for…of`、`return`(顶层=终止;函数内=返回值)、`break` 等 JS 对齐形态
|
|
290
|
+
- 函数:`function f(params) { … }` 纯逻辑体 + `await f(args)` 调用(return 落 `steps.<id>.data`)——函数体禁副作用内置/嵌套调用/外层步骤引用(v1 裁剪)
|
|
291
|
+
- 模块:`import { x } from 'wf://std/…'`(内置库)+ `import { x } from 'https://…'`(远程——compileWfjs({ remoteFetch }) 编译期抓取快照物化,运行期零 IO;白名单仅 `{ functions }` 根)+ `export { f }`(函数库形态)
|
|
292
|
+
- 约束:`std` 函数需 import 才可见(ESM 一致);表达式内仅 std 纯函数(副作用内置仅语句层);**作用域对齐 JS**——块级遮蔽(mangle 内部名 x$N——round-trip 稳定)、函数提升、参数同名覆盖全局、不同函数参数互不干扰(v2)
|
|
293
|
+
|
|
294
|
+
**视图适配**(`workflow/views.ts`——纯数据转换,组件库零耦合——DSL 是枢纽真相,视角即适配):
|
|
295
|
+
|
|
296
|
+
- `workflowToDag(def)` → `{nodes, edges}`——Pipeline 组件直接消费(v0:顶层线性链 + 子链折叠进标签 `条件 i(then×2/else×1)`)
|
|
297
|
+
- `toJsonSchema(stepSchemas())` → JsonSchema 对象——JsonSchemaForm 直接消费(按步骤类型组织 properties)
|
|
298
|
+
|
|
299
|
+
**执行**:Runner 递归子链(assign/if/while/for/return/call)+ 局部 vars + 循环栈(嵌套恢复)+ maxIters 默认 1000 + 函数递归深度 64。
|
|
300
|
+
|
|
301
|
+
**裁剪(诚实)**:嵌套函数定义(函数体内 function);函数体内返回 await 调用(return 值是表达式——副作用调用仅语句层);远程模块不支持嵌套导入/环检测(纯数据单层);DAG/子 workflow 结构化可编程为 v2 候选;框架只做执行,REST/UI/多租户/对话生成由消费方接入(agent-platform 第二阶段)。
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
110
305
|
> **运行**:`npm run test:server`(163 契约)· 直跑
|
|
111
306
|
> `node --env-file=.env --test src/server/core/*.test.ts src/shared/router/*.test.ts`
|
|
112
307
|
> —— db 真库依赖 docker(无 docker 跑 `src/server/core/*.test.ts` 子集)。
|
|
308
|
+
|
|
309
|
+
### 5.3 确定性契约(ORM 三组件——状态机管理原则)
|
|
310
|
+
|
|
311
|
+
> **orm = shape + operator + adapter**——三组件内部状态确定,采用状态机管理
|
|
312
|
+
> (透明·可控·健壮)。**一致性铁律**:同一声明/同一算子在任何 adapter 行为
|
|
313
|
+
> 等价——不一致即 bug(修复而非登记);声明了但无行为 = 不透明 = 定案
|
|
314
|
+
> (实现或移除——softDelete 判负先例:零行为 → 移除)。
|
|
315
|
+
|
|
316
|
+
**shape(单态冻结)**:构造即终态(不可变);平台唯一合法形态 `satisfies
|
|
317
|
+
ZodRawShape`(§5.2);行类型单源 `RowOf<SHAPES.xxx>`。
|
|
318
|
+
|
|
319
|
+
**operator(无状态纯函数)**:定义即值;值形态唯一(`{ eq: v }` / `{ in: [...] }`
|
|
320
|
+
/ `{ isNull: true }`——禁裸标量/数组/null);**`{ eq: null }` = IS NULL**(双端
|
|
321
|
+
判空一致——真库不再编译 `= NULL` 恒假)。
|
|
322
|
+
|
|
323
|
+
**adapter(真状态机)**:
|
|
324
|
+
- MemoryTable 状态:`absent → (applySchema/DDL) declared` · `absent → (insert
|
|
325
|
+
直建) observed`;observed 上 DDL 拒绝(对齐操作才允许)——`inspectTable()`
|
|
326
|
+
状态可见面(契约断言)
|
|
327
|
+
- 行为矩阵:select 列校验 = 声明列集 vs 行键事实 · jsonb 解码 = columnTypes
|
|
328
|
+
判定 vs 行键值启发 · ddl = alter vs 拒绝
|
|
329
|
+
|
|
330
|
+
**租户 scope(W1)**:
|
|
331
|
+
```ts
|
|
332
|
+
const pg = postgres({
|
|
333
|
+
tenant: { field: 'app_id', value: (ctx) => (ctx as AppCtx).appId },
|
|
334
|
+
})
|
|
335
|
+
// 中间件自动:ctx.orm = orm.withCtx(ctx)——ctxTable 面自动 scope:
|
|
336
|
+
// select/update/delete 预置 tenant 条件 · insert 自动注入字段
|
|
337
|
+
const agents = await ctx.orm.ctxTable('agents').paginate({ limit, offset })
|
|
338
|
+
```
|
|
339
|
+
- scope 仅在 `ctxTable`(`table` 是无 scope 基础面——显式)
|
|
340
|
+
- **惰性求值**:tenant.value 每操作时读 ctx 当前值(pg 中间件先于 auth
|
|
341
|
+
注册——appId 后注入——scope 仍生效)
|
|
342
|
+
|
|
343
|
+
**事务(W2)**:
|
|
344
|
+
```ts
|
|
345
|
+
await ctx.orm.transaction(async (tx) => {
|
|
346
|
+
const T = tables(tx) // tx 内表操作同连接/同链
|
|
347
|
+
await T.a.insert(...).run() // 任一失败整链回滚
|
|
348
|
+
await T.b.insert(...).run()
|
|
349
|
+
})
|
|
350
|
+
```
|
|
351
|
+
- 真库:同连接真事务 · memory:单线程无并发交错(no-op 等价——诚实标注)
|
|
352
|
+
- 多写场景(survey 提交/演示空间 7 写)试点收口——先包原子链再加写
|
|
353
|
+
|
|
354
|
+
**分页(W5)**:`orm.ctxTable('x').paginate({ filter, sort, limit, offset })` →
|
|
355
|
+
`{ rows, total }`——count+list 单调用(同一 scope 面——不手写双查)
|
|
356
|
+
|
|
357
|
+
**协议面 helpers(W0-W4 体验提升——handler 样板消亡)**:
|
|
358
|
+
```ts
|
|
359
|
+
// body:shape → 校验(手写 body 类型/必填/枚举校验消亡——build 面 30 行 → 1 行)
|
|
360
|
+
const body = await bodyOf(req, T.agents, { variant: 'insert', omit: ['app_id'] })
|
|
361
|
+
// variant: 'insert'(缺省——裸行)| 'patch'(全可选——部分更新)
|
|
362
|
+
// omit:系统列(租户/服务端注入面)——变体生成时剔除(required 豁免——
|
|
363
|
+
// body 传该键被忽略而非校验——注入面权威)
|
|
364
|
+
// 错误:ValidationError(400 面——字段路径+期望可读)
|
|
365
|
+
// 类型面:BodyOf<typeof shape>(字段名/值类型/枚举编译期精确——auto 列
|
|
366
|
+
// (f.pk/f.now)自动省略)
|
|
367
|
+
|
|
368
|
+
// query:URL → list 参数(手写 parseInt/枚举白名单/sort 解析消亡)
|
|
369
|
+
const { filter, sort, limit, offset } = listQuery(url, T.agents, { defaultLimit: 50 })
|
|
370
|
+
|
|
371
|
+
// 错误:catch 样板消亡(状态码映射——DbError validation→400 · 23505→409)
|
|
372
|
+
catch (e) { return errorResponse(e) }
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**undefined 值契约(W2 定案)**——`{ eq: undefined }` 等 everywhere 显式
|
|
376
|
+
拒绝(不静默跳过/不序列化分裂):`省略该键或显式 isNull`——四层同语义
|
|
377
|
+
(filterToWhere / qb where 入口 / compileWhere / memory 执行入口——空表不
|
|
378
|
+
逃逸——声明即校验);update 的 **set** 值 undefined 保留(部分更新面);
|
|
379
|
+
判空显式 `isNull`(`{ eq: null }` = IS NULL)。
|
|
380
|
+
|
|
381
|
+
**sort 类型化(W3)**:`paginate.sort.field: keyof S`——字面量多余键编译红。
|
|
382
|
+
|
|
383
|
+
**一致性诊断(W3——启动对齐面)**:
|
|
384
|
+
```ts
|
|
385
|
+
const r = await pg.checkConsistency() // 零参数——orm 注册表自动枚举
|
|
386
|
+
// r.issues:表/列缺失 error(必须修)· 类型不匹配 warn(宽等价组不误报)·
|
|
387
|
+
// 库残留表/列 warn · r.ok = 无 error
|
|
388
|
+
```
|
|
389
|
+
惰性注册纪律:route 时才 `tables(orm)` 的注册——诊断前先 `tables(orm)`
|
|
390
|
+
先行(否则空声明——全表 extra warn——诚实提示)。
|
|
391
|
+
|
|
392
|
+
**判负登记(体验提升计划——推翻条件见 plan/orm-体验提升.md)**:BodyOf 的
|
|
393
|
+
required/optional 精确性(TS 条件类型惰性——运行时校验权威)· undefined
|
|
394
|
+
编译期拒绝(无 exactOptionalPropertyTypes)· route 生成器(业务纪律——
|
|
395
|
+
只提取样板层)· 平台 23 处全量迁移(试点 2 处——诚实判据:平台无纯 CRUD
|
|
396
|
+
可迁)。
|
|
397
|
+
|
|
398
|
+
**透明面(诚实裁剪)**:
|
|
399
|
+
- FK 约束:memory 无校验面(真库 23503 → 400)——跨表引用依赖先插主行
|
|
400
|
+
- vectorScore 精度:内存余弦 vs 真库 ivfflat 近似——结果排序可能差异
|
|
401
|
+
- tx/ctx 派生面:`table(name)` 免 shapeDef(registry 共享——同名表幂等)
|
|
402
|
+
|
|
403
|
+
### 5.4 声明式 API 面(orm.gql / orm.rest——命名契约)
|
|
404
|
+
|
|
405
|
+
> **设计分层纪律**:HTTP client ←→ 协议面(graphql/restful)←→ handler(业务面)
|
|
406
|
+
> ←→ orm(数据面)。自动生成**不是绕过 handler**,是 **handler 默认实现的生成器**
|
|
407
|
+
> (解析→校验→租户→orm→响应→错误语义);业务(权限/事务/编排/响应定制)
|
|
408
|
+
> 永远手写——以覆盖/钩子接缝在生成面之上。
|
|
409
|
+
>
|
|
410
|
+
> **判别标准(一票)**:_「除了 参数→orm→响应 还有别的行为吗?」有 → 手写;
|
|
411
|
+
> 没有 → 生成。_
|
|
412
|
+
|
|
413
|
+
**命名契约(一个词一个概念——学一个面等于学三个面)**:
|
|
414
|
+
|
|
415
|
+
| 概念 | 命名 | 说明 |
|
|
416
|
+
| --- | --- | --- |
|
|
417
|
+
| 数据面 | `shape`(f/z) | 唯一事实源 |
|
|
418
|
+
| 代码内查询 | `orm.table` / `typedQuery` | 类型化/跨表 |
|
|
419
|
+
| GraphQL 面 | `orm.gql(Table)` / `gqlFromShape(shapeDef, opts)` | 入口对称 |
|
|
420
|
+
| RESTful 面 | `orm.rest(Table)` / `restFromShape(shapeDef, opts)` | 入口对称 |
|
|
421
|
+
| filter→WhereExpr | `filterToWhere(filter, shapeDef)` | 三面共享单源 |
|
|
422
|
+
| 字段策略 | `fieldPolicy`(`hidden?: string[]` 首版) | 敏感列豁免——三面生效 |
|
|
423
|
+
|
|
424
|
+
**规则**:① 入口对称 `orm.<协议小写词>` ② 生成器对称 `<协议>FromShape`
|
|
425
|
+
③ `create*` 构造对象 / `*FromShape` 派生协议面——不混用 ④ 协议短名(gql/rest)
|
|
426
|
+
仅用于 orm 入口与生成器;HTTP 层协议中间件全称(graphql()/ui()/ws())。
|
|
427
|
+
|
|
428
|
+
**gql 面(已有——`orm.gql(Table)`)**:shape → SDL + resolvers;Query
|
|
429
|
+
(list/one——filter/sort/limit/offset)· Mutation(insert/update/delete);
|
|
430
|
+
租户 scope 自动注入;校验(insertSchema/updateSchema——API 边界);
|
|
431
|
+
`hidden` 字段豁免(敏感列 SDL/返回双面不出现)。
|
|
432
|
+
|
|
433
|
+
**rest 面(W4——`orm.rest(Table)`)**:shape 派生路由组(list/one/
|
|
434
|
+
insert/update/delete——query 参数 schema/limit clamp/枚举白名单/404·204
|
|
435
|
+
语义/hooks before-after/字段策略)。
|
|
436
|
+
|
|
437
|
+
**真实化承诺**:SDL/路由表是 **shape 投影**——「声明了就有行为」,没有
|
|
438
|
+
「声明但静默跳过」(vector 列显式 [Float!]、未知字段显式报错)。
|
|
439
|
+
|
|
440
|
+
### 5.4.1 用法(两分钟上手)
|
|
441
|
+
|
|
442
|
+
**gql 面**(`/api/gql`——租户 scope/hidden/校验全自动):
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
const agentGql = pg.orm.gql(pg.orm.table('agents'), { hidden: ['webhook_secret'] })
|
|
446
|
+
app.graphql('/api/gql', async (req, ctx) => ({
|
|
447
|
+
schema: agentGql.typeDefs, // SDL(shape 投影——声明即有行为)
|
|
448
|
+
resolvers: agentGql.resolvers,
|
|
449
|
+
context: () => ({ orm: ctx.orm, appId: ctx.appId }), // tenant.value 读 appId
|
|
450
|
+
}))
|
|
451
|
+
// query { agentsList(filter: {type: {eq: ai}}, sort: [{field: created_at, dir: desc}], limit: 10) { id name } }
|
|
452
|
+
// mutation { agentsInsert(data: {type: ai, name: "x"}) { id } } // app_id 自动注入
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**rest 面**(`orm.rest(Table).mount(app, base)`——5 个标准路由):
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
pg.orm.rest(pg.orm.table('agents'), {
|
|
459
|
+
hidden: ['webhook_secret'],
|
|
460
|
+
hooks: {
|
|
461
|
+
beforeList: (req, ctx) => { if (!canRead(ctx)) throw new Error('403') }, // 业务守卫
|
|
462
|
+
afterList: async (rows) => rows.map((r) => ({ ...r, annotated: true })), // 响应增强
|
|
463
|
+
},
|
|
464
|
+
}).mount(app, '/api/rest-agents')
|
|
465
|
+
// GET /api/rest-agents?type=ai&sort=-created_at&limit=20 → { agents, total }
|
|
466
|
+
// GET /api/rest-agents/:id → 200/404(跨租户 404)
|
|
467
|
+
// POST /api/rest-agents → 201(app_id 自动注入·校验 400)
|
|
468
|
+
// PATCH /api/rest-agents/:id → 200/404
|
|
469
|
+
// DELETE /api/rest-agents/:id → 204/404
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**手写 route 三件套**(W0-W4 体验提升——样板层收口后手写面长这样;与
|
|
473
|
+
gql/rest 生成面同源 shape 变体——校验语义一致):
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
app.post('/api/agents', async (req: Request, ctx: AppCtx): Promise<Response> => {
|
|
477
|
+
const T = tables(orm)
|
|
478
|
+
try {
|
|
479
|
+
// body:30 行手写类型 + 必填/枚举校验 → 1 行(shape 单源——app_id 系统列 omit)
|
|
480
|
+
const body = await bodyOf(req, T.agents, { variant: 'insert', omit: ['app_id'] })
|
|
481
|
+
const [agent] = await T.agents.insert({ ...业务组装 }).returning('id', 'name').run()
|
|
482
|
+
return Response.json({ agent }, { status: 201 })
|
|
483
|
+
} catch (e) { return errorResponse(e) } // DbError → 400/409(23505 唯一冲突)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
app.get('/api/agents', async (req: Request, ctx: AppCtx): Promise<Response> => {
|
|
487
|
+
const { orm } = ctx
|
|
488
|
+
try {
|
|
489
|
+
const { filter, sort, limit, offset } = listQuery(new URL(req.url), tables(orm).agents)
|
|
490
|
+
const page = await orm.ctxTable('agents').paginate({ filter, sort, limit, offset })
|
|
491
|
+
return Response.json({ agents: page.rows, total: page.total })
|
|
492
|
+
} catch (e) { return errorResponse(e) }
|
|
493
|
+
})
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
- `bodyOf`:enum/必填/nullable/auto 列(f.pk/f.now)省略——校验失败 400 可读
|
|
497
|
+
(字段路径+期望)· `variant: 'patch'` = 全可选部分更新
|
|
498
|
+
- `listQuery`:eq 直排 · sort `-field` 多字段 · limit clamp(默认 20/max 100 可配)·
|
|
499
|
+
枚举白名单(非法值显式 400——不静默忽略)
|
|
500
|
+
- `errorResponse`:catch 一行收口(显式 status 优先——业务守卫 403)
|
|
501
|
+
|
|
502
|
+
### 5.4.2 边界判别(写业务前先问)
|
|
503
|
+
|
|
504
|
+
**一票判别**:_「除了 参数→orm→响应 还有别的行为吗?」_
|
|
505
|
+
|
|
506
|
+
| 场景 | 判别 | 平台实证 |
|
|
507
|
+
| --- | --- | --- |
|
|
508
|
+
| agents/departments list(成员计数/最近消息/token 聚合) | 有聚合 → **手写** | departments.ts:17(组查+Map 合并) |
|
|
509
|
+
| agents/resp 校验业务逻辑(DM 编排/from-template) | 有业务 → **手写** | departments.ts:72 / role-templates.ts:180 |
|
|
510
|
+
| 新纯 CRUD 表(无聚合/无跨表/无权限分支) | 无 → **生成** | role_templates(当前无路由消费——未来表化直接 rest/gql) |
|
|
511
|
+
| 权限守卫/响应增强 | **hooks 接缝**(before*/after*) | agents-rest 试点 |
|
|
512
|
+
| 跨表查询(join/子查询) | typedQuery(手写——关系面判负) | agents.ts stats 聚合 |
|
|
513
|
+
|
|
514
|
+
**接缝覆盖序**:hooks(before/after)→ 覆盖 resolvers map(gql)→ 整路由
|
|
515
|
+
替换(rest——mount 前不注册同名路径)。
|
|
516
|
+
|
|
517
|
+
### 5.4.3 三条铁律
|
|
518
|
+
|
|
519
|
+
1. **租户列表在 gql/rest 面恒可缺省**——服务端自动注入(SDL/校验面不
|
|
520
|
+
强制客户端传 app_id;显式传=优先)
|
|
521
|
+
2. **无租户列的表(全局表)在 tenant 中间件下不 scope**——不注入不存在的
|
|
522
|
+
列(role_templates/workflows 可用)
|
|
523
|
+
3. **hidden 列三面不出现**——SDL 不生成/路由不返回/未知字段显式报错
|
|
524
|
+
(不可查询≠静默丢弃——声明即行为)
|
|
525
|
+
|
|
526
|
+
### 5.4.4 判负清单(可被新论证推翻)
|
|
527
|
+
|
|
528
|
+
- json 字段标量化:不做——String 序列化(平台 12 jsonb 列 0 gql 消费)
|
|
529
|
+
- uuid→ID 标量:不做——String 面已契约化(迁移破坏旧契约无收益)
|
|
530
|
+
- 关系面(JOIN/嵌套对象):不做(typedQuery 集成复杂度高且无消费者)
|
|
531
|
+
- connection/cursor 分页:不做——rows+total 对齐 paginate(数据量级 <10^4)
|
|
532
|
+
- subscription:不做(ws 中间件已有订阅面)
|
|
533
|
+
- graphql 包移除(自研执行器):不做——graphql ^16 生态成熟(与
|
|
534
|
+
esbuild/ws 同级——非四类核心内存化矩阵范围)
|
|
535
|
+
- 嵌套路由 REST(/api/agents/:id/skills):不做——rest 路由是表平面
|
|
536
|
+
- 批量 upsert mutation:不做——无场景(workflow 批量走 query 面)
|
|
537
|
+
|
|
538
|
+
### 5.4.5 执行实录(gql/rest 提升——2027-xx)
|
|
539
|
+
|
|
540
|
+
- **I1** `{eq:null}` 被吞 → `isNull`(Filter 桥与 O1 判空单源——filterToWhere)
|
|
541
|
+
- **I2** sort SDL `[SortInput!]` 声明 vs resolver 只取 sort[0] → 多字段链真实化
|
|
542
|
+
- **I3** gql 租户双源(gqlFromShape opts.tenant vs createOrm tenant)→
|
|
543
|
+
orm.gql 自动派生 createOrm.tenant(单源——gql 不可绕过租户隔离)
|
|
544
|
+
- **I4** enum 列无过滤面 → Filter 值面 GraphQL enum(eq/ne/in/notIn/isNull)
|
|
545
|
+
- **I5** vector 列 SDL 静默消失 → `[Float!]`(zodToGql 认识 ZodVector)
|
|
546
|
+
- **W4 执行实录**(rest 面):ctxTable 兼容(无 CtxOrm → table);POST/PATCH
|
|
547
|
+
校验与映射单源(ctxTable 内部——rest 前置 parse 时序错已修);错误 catch →
|
|
548
|
+
400 语义;migrateModule 列需带 meta(memory observed 态无 pk 补位——
|
|
549
|
+
t.pk 仅 declared 设置——平台 migrate 同构)
|
|
550
|
+
- **W5 执行实录**(平台试点):① isOptional 缺 ZodNullable(insert input
|
|
551
|
+
nullable 列 required 化——agents 20+ 列)② insert 租户注入先于校验
|
|
552
|
+
(parse 拒绝缺省 app_id)③ 租户列 SDL 声明 required → 恒可选 ④ CtxOrm
|
|
553
|
+
全局表注入不存在的列 → 字段不存在不 scope ⑤ dist 重建纪律(平台经
|
|
554
|
+
dist 引框架——改 src 后必须 build——本轮 3 次 build 教训)
|
|
555
|
+
- **盲区教训**:契约层 enum 测试只测 filter(输入面)——输出序列化/字面量
|
|
556
|
+
输入(GraphQL 规范:enum 不带引号)首见于平台试点——盲区先补测再信绿
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weifuwu",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.91.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
|
".": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
},
|
|
17
17
|
"./components/style.css": "./dist/client/components/style.css",
|
|
18
18
|
"./dev": "./dist/dev/index.js",
|
|
19
|
+
"./workflow": {
|
|
20
|
+
"types": "./dist/server/workflow/index.d.ts",
|
|
21
|
+
"default": "./dist/server/workflow/index.js"
|
|
22
|
+
},
|
|
19
23
|
"./vdom": {
|
|
20
24
|
"types": "./dist/client/vdom/index.d.ts",
|
|
21
25
|
"default": "./dist/client/vdom/index.js"
|
|
@@ -43,7 +47,7 @@
|
|
|
43
47
|
"release:dry": "node scripts/release.mjs --dry-run",
|
|
44
48
|
"test:client": "node --env-file=.env --test --test-concurrency=4 'src/test/contract/**/*.test.ts'",
|
|
45
49
|
"test:server": "node --env-file=.env --test --experimental-test-module-mocks --test-concurrency=8 'src/server/**/*.test.ts'",
|
|
46
|
-
"test:showcase": "node --env-file=.env --test --test-timeout=8000 apps/showcase/test/comp-*.test.ts",
|
|
50
|
+
"test:showcase": "node --env-file=.env --test --test-concurrency=6 --test-timeout=8000 apps/showcase/test/comp-*.test.ts",
|
|
47
51
|
"test:scenario": "node --env-file=.env --test --test-timeout=8000 --test-concurrency=14 'src/test/scenario/e2e*.test.ts'",
|
|
48
52
|
"test:components": "node --env-file=.env --test --test-timeout=8000 --test-concurrency=2 src/test/scenario/e2e-6.test.ts",
|
|
49
53
|
"audit:semantics": "node scripts/audit-core-semantics.mjs",
|
|
@@ -54,7 +58,9 @@
|
|
|
54
58
|
"audit:theme": "node scripts/audit-theme.mjs",
|
|
55
59
|
"audit:api": "node scripts/audit-api.mjs",
|
|
56
60
|
"audit:bundle": "node scripts/audit-bundle.mjs",
|
|
57
|
-
"audit:vdom": "node scripts/audit-vdom.mjs"
|
|
61
|
+
"audit:vdom": "node scripts/audit-vdom.mjs",
|
|
62
|
+
"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"
|
|
58
64
|
},
|
|
59
65
|
"dependencies": {
|
|
60
66
|
"esbuild": "^0.28.1",
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { DBServer } from './server.ts';
|
|
2
|
-
export interface PostgresServerOptions {
|
|
3
|
-
/** 监听端口(0 = 随机)。默认 0。 */
|
|
4
|
-
port?: number;
|
|
5
|
-
/** 认证口令(无密码 = AuthenticationOk 直过)。默认无。 */
|
|
6
|
-
password?: string;
|
|
7
|
-
/** 数据库名(startup 参数校验)。默认接受任意。 */
|
|
8
|
-
database?: string;
|
|
9
|
-
}
|
|
10
|
-
export declare class MemoryPostgresServer implements DBServer {
|
|
11
|
-
port: number;
|
|
12
|
-
url: string;
|
|
13
|
-
private server;
|
|
14
|
-
private engine;
|
|
15
|
-
private opts;
|
|
16
|
-
private closed;
|
|
17
|
-
private queryLog;
|
|
18
|
-
private txSnap;
|
|
19
|
-
/** 存活连接(close 时全量销毁——server.close 只停 accept,不等存量连接) */
|
|
20
|
-
private sockets;
|
|
21
|
-
/** 命名 prepared statement 缓存(客户端复用——同 sql 不发 Parse) */
|
|
22
|
-
private statements;
|
|
23
|
-
/** portal → statement 绑定(Bind 创建、Execute 按 portal 执行——客户端 portal 通常空名) */
|
|
24
|
-
private portals;
|
|
25
|
-
constructor(options?: PostgresServerOptions);
|
|
26
|
-
start(): Promise<void>;
|
|
27
|
-
close(): Promise<void>;
|
|
28
|
-
/** 测试辅助:执行过的 SQL 列表 */
|
|
29
|
-
get queries(): string[];
|
|
30
|
-
private handleSocket;
|
|
31
|
-
/** startup:196608(协议 3.0)+ 参数 k\0v\0... */
|
|
32
|
-
private handleStartup;
|
|
33
|
-
private handleMessage;
|
|
34
|
-
/** 执行 SQL(MemorySql 引擎——async 异常转 { error }) */
|
|
35
|
-
private executeSql;
|
|
36
|
-
/** 运行查询并编码应答 */
|
|
37
|
-
private runQuery;
|
|
38
|
-
private writeQueryResult;
|
|
39
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* weifuwu/db — SQL Parser(字符串 → Query Language AST)
|
|
3
|
-
*
|
|
4
|
-
* 架构闭环(单一中间表示):
|
|
5
|
-
* SQL 字符串 → parseSqlToAst(本文件:tokenizer + 递归下降)
|
|
6
|
-
* → Query Language AST
|
|
7
|
-
* ├─ compileQuery:AST → 参数化 SQL(真库 stringify)
|
|
8
|
-
* └─ executeQuery:AST → 内存直执行(MemorySql)
|
|
9
|
-
*
|
|
10
|
-
* 覆盖(系统性语法规则——非正则打补丁):
|
|
11
|
-
* SELECT(投影/别名/常量/表达式/UNION ALL/派生表/WHERE 全操作符 + AND OR/
|
|
12
|
-
* IS NULL/IN/ORDER BY/LIMIT)
|
|
13
|
-
* INSERT(多行 VALUES/RETURNING/无列名)
|
|
14
|
-
* UPDATE / DELETE(SET/WHERE/RETURNING)
|
|
15
|
-
* 表达式:$n 参数/cast(::type)/算术(加减乘除)/now()/引号字符串
|
|
16
|
-
*
|
|
17
|
-
* 诚实裁剪:JOIN/GROUP BY/HAVING/子查询(EXISTS/IN 子查询)/窗口函数——
|
|
18
|
-
* 抛 ProtocolError(真库/Query Language 结构化路径使用)。
|
|
19
|
-
*/
|
|
20
|
-
import type { Query, WhereExpr } from './query.ts';
|
|
21
|
-
export declare function parseSqlToAst(sql: string, params?: unknown[]): Query;
|
|
22
|
-
/** WHERE 子句 → Query Language WhereExpr(OR 拆分 → AND 组合 → 条件)
|
|
23
|
-
* 同列多条件走 addWhereCond(对象级合并 / and 包装——不覆盖不静默丢弃) */
|
|
24
|
-
export declare function parseWhereToExpr(clause: string, params: unknown[], alias?: string): WhereExpr;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface TestServers {
|
|
2
|
-
redis: {
|
|
3
|
-
port: number;
|
|
4
|
-
url: string;
|
|
5
|
-
};
|
|
6
|
-
pg: {
|
|
7
|
-
port: number;
|
|
8
|
-
url: string;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
export declare function startTestServers(): Promise<TestServers>;
|
|
12
|
-
/** 仅 Redis(不需要 PG 的测试文件) */
|
|
13
|
-
export declare function startRedisServer(): Promise<{
|
|
14
|
-
port: number;
|
|
15
|
-
url: string;
|
|
16
|
-
}>;
|
|
17
|
-
/** 仅 PG(不需要 Redis 的测试文件) */
|
|
18
|
-
export declare function startPgServer(): Promise<{
|
|
19
|
-
port: number;
|
|
20
|
-
url: string;
|
|
21
|
-
}>;
|