259 lines
5.7 KiB
Markdown
259 lines
5.7 KiB
Markdown
# 3-3 [process] Rust Web Tree Realtime Event Stream 方案 v1
|
|
|
|
> 更新时间:2026-04-17
|
|
>
|
|
> 关联文档:
|
|
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
|
|
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`
|
|
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md`
|
|
|
|
## 1. 目标
|
|
|
|
这份文档用于固定 Stage C-1 的正式实时链路口径:
|
|
|
|
- 保留 Convex 作为 realtime substrate
|
|
- Rust 成为 tree-first graph 的 semantic owner
|
|
- Rust Web 负责正式页面 transport 与实时事件流
|
|
- 前端只消费 projection 与 delta,不再消费实验壳真相
|
|
|
|
目标效果是接近网页版 wolai / notion 的体验:
|
|
|
|
- 快速进入页面
|
|
- 树结构实时同步
|
|
- 局部变化快速响应
|
|
- 浏览器端不持有第二套树真相
|
|
|
|
## 2. 职责划分
|
|
|
|
### 2.1 Convex substrate
|
|
|
|
Convex 继续承担:
|
|
|
|
- 持久化
|
|
- mutation / query 底座
|
|
- 实时订阅底座
|
|
- 文件 / 对象存储协作
|
|
|
|
Convex 在这里是:
|
|
|
|
> storage / realtime substrate
|
|
|
|
而不是页面树语义 owner。
|
|
|
|
### 2.2 Rust semantic owner
|
|
|
|
Rust kernel 负责:
|
|
|
|
- `page_tree / sidebar_tree / file_tree / subtree` 语义
|
|
- node / edge / projection 规则
|
|
- command / query 的语义收口
|
|
- domain event 的统一格式
|
|
- trace / audit / version 口径
|
|
|
|
Rust 在这里是:
|
|
|
|
> Rust semantic owner
|
|
|
|
### 2.3 Rust Web transport
|
|
|
|
Rust Web 负责:
|
|
|
|
- SSR 页面壳
|
|
- SSE / WS 主链
|
|
- workspace / subtree 订阅入口
|
|
- 把 Convex 订阅与 Rust domain event 连接起来
|
|
- 向前端输出稳定的 projection snapshot + delta stream
|
|
|
|
### 2.4 前端 projection consumer
|
|
|
|
前端只负责:
|
|
|
|
- 首屏渲染
|
|
- islands 交互
|
|
- optimistic UI
|
|
- 应用 delta 到本地 projection cache
|
|
|
|
前端不再负责:
|
|
|
|
- 重新定义树结构
|
|
- 重新计算页面树语义
|
|
- 维护实验壳 iframe 作为正式实时主链
|
|
|
|
## 3. 为什么当前 tree shell 不是正式实时链路
|
|
|
|
当前 `mnote-web tree shell` 仍然只是实验壳,原因有三点:
|
|
|
|
1. 它依赖 iframe / HTML shell / postMessage 交互。
|
|
2. 它的 command path 和状态边界更接近实验 viewer,而不是正式页面 transport。
|
|
3. 它没有成为首页、sidebar、文档页共享的正式实时订阅主链。
|
|
|
|
因此当前 tree shell 可以继续保留为:
|
|
|
|
- 实验验证壳
|
|
- 独立 tree viewer
|
|
- picker / filetree 的可选增强壳
|
|
|
|
但不能继续当成正式 realtime 主链。
|
|
|
|
## 4. 正式 realtime tree event stream 分层
|
|
|
|
正式链路建议固定为四层:
|
|
|
|
### 4.1 Convex 持久化/订阅底座
|
|
|
|
这里负责:
|
|
|
|
- 命令落账
|
|
- 持久化页面树状态
|
|
- 输出 mutation 后可订阅的数据变化
|
|
|
|
### 4.2 Rust kernel 语义 owner
|
|
|
|
这里负责:
|
|
|
|
- 把命令结果解释为 domain event
|
|
- 把底层 mutation 变化转成树域可消费的语义事件
|
|
- 维护 projection rebuild 与 delta 生成规则
|
|
|
|
### 4.3 Rust Web SSE/WS transport
|
|
|
|
这里负责:
|
|
|
|
- 暴露正式 `/api/tree/events` 或等价 stream 入口
|
|
- 管理 workspace / subtree 订阅
|
|
- 发送 snapshot、delta、cursor、ack、resync 信号
|
|
|
|
推荐策略:
|
|
|
|
- 首屏与重连优先使用 snapshot
|
|
- 连续变化优先推送 delta
|
|
- 大规模漂移或 cursor 失配时回退到 resync snapshot
|
|
|
|
### 4.4 前端 projection consumer
|
|
|
|
这里负责:
|
|
|
|
- 接收初始 projection snapshot
|
|
- 接收增量事件
|
|
- 合并到 sidebar / filetree / page subtree cache
|
|
- 必要时触发局部重渲染
|
|
|
|
## 5. 建议事件形状
|
|
|
|
正式事件建议至少覆盖三类 scope:
|
|
|
|
- workspace
|
|
- subtree
|
|
- tree delta
|
|
|
|
### 5.1 Workspace stream
|
|
|
|
用于:
|
|
|
|
- 当前工作区根树更新
|
|
- 垃圾桶、模板、共享页等全局区域变化
|
|
|
|
建议形状:
|
|
|
|
```json
|
|
{
|
|
"stream": "workspace",
|
|
"workspaceId": "ws_123",
|
|
"cursor": "evt_1001",
|
|
"kind": "snapshot|delta|resync",
|
|
"projection": "sidebar_tree",
|
|
"data": {}
|
|
}
|
|
```
|
|
|
|
### 5.2 Subtree stream
|
|
|
|
用于:
|
|
|
|
- 当前文档子树
|
|
- file tree 局部展开区域
|
|
- picker 只关注的局部节点集合
|
|
|
|
建议形状:
|
|
|
|
```json
|
|
{
|
|
"stream": "subtree",
|
|
"workspaceId": "ws_123",
|
|
"rootNodeId": "page_1",
|
|
"cursor": "evt_1002",
|
|
"kind": "snapshot|delta|resync",
|
|
"projection": "page_tree",
|
|
"data": {}
|
|
}
|
|
```
|
|
|
|
### 5.3 Tree delta payload
|
|
|
|
建议包含:
|
|
|
|
- `eventId`
|
|
- `commandId`
|
|
- `traceId`
|
|
- `aggregateType`
|
|
- `aggregateId`
|
|
- `op`
|
|
- `node`
|
|
- `parentNodeId`
|
|
- `position`
|
|
- `removedNodeIds`
|
|
- `changedProjectionKeys`
|
|
|
|
参考形状:
|
|
|
|
```json
|
|
{
|
|
"eventId": "evt_1003",
|
|
"commandId": "cmd_2001",
|
|
"traceId": "trace_3001",
|
|
"aggregateType": "page",
|
|
"aggregateId": "page_9",
|
|
"op": "create|rename|move|delete|restore|purge|embed",
|
|
"node": {
|
|
"id": "page_9",
|
|
"title": "新页面"
|
|
},
|
|
"parentNodeId": "page_root",
|
|
"position": 3,
|
|
"removedNodeIds": [],
|
|
"changedProjectionKeys": ["sidebar_tree", "file_tree"]
|
|
}
|
|
```
|
|
|
|
## 6. 与首页和首屏的关系
|
|
|
|
首页与 `/api/sidebar` 的 P0 原则必须保持不变:
|
|
|
|
- 首页不能再依赖 3104 实验壳
|
|
- `3000` 主入口必须可稳定进入 `/auth` 或文档页
|
|
- 实时链路失败时,首屏仍要有稳定 snapshot fallback
|
|
|
|
也就是说:
|
|
|
|
- 首屏可用性优先于实时增强
|
|
- realtime stream 是正式增强层,不是新的首屏阻塞点
|
|
|
|
## 7. 实施顺序
|
|
|
|
推荐顺序如下:
|
|
|
|
1. 先固定首页、sidebar、picker 不再依赖 tree shell 实验壳。
|
|
2. 再固定 Rust kernel 的 tree command / projection owner 身份。
|
|
3. 然后补正式 Rust Web SSE/WS tree stream。
|
|
4. 最后把 sidebar、page subtree、filetree 逐步切到统一 stream。
|
|
|
|
## 8. 最终固定口径
|
|
|
|
长期口径固定为:
|
|
|
|
- Convex substrate 不拆
|
|
- Rust semantic owner 收口语义
|
|
- Rust Web 提供正式实时 transport
|
|
- 前端只消费 projection snapshot 与 tree delta
|
|
- iframe/postMessage tree shell 不是正式 realtime 主链
|