VBI Provider Review

面向 apps/vbi_provider 的静态调研报告:覆盖 Provider SDK 暴露接口、REST 与协作通道契约、Chart/Insight/Report 数据流、Agent Workspace、测试覆盖和 review 风险点。

日期:2026-05-24 包名:@visactor/headless-bi-provider 源码:apps/vbi_provider/src 后端契约:apps/vbi_be/src

结论摘要

定位

VBI Provider 是一个库型 SDK,不是独立 HTTP 服务。它把平台资源抽象成三类一等 Provider: ChartProvider、InsightProvider、ReportProvider。

核心关系

固定调用关系是 client -> provider -> builder。列表和元信息走 REST,编辑能力通过 REST 取 session 后进入 Hocuspocus/Yjs 协作文档。

Review 重点

最大风险不在接口数量,而在 REST 直接变更与已打开协作文档之间的状态一致性,以及 WebSocket 鉴权/headers 没有贯通。

模块地图

模块 职责 关键文件
client 创建三类 provider,并提供资源列表入口。 apps/vbi_provider/src/client.ts
types 定义 SDK public surface:client options、resource、chart、insight、report、remote session。 apps/vbi_provider/src/types/*.ts
remote 统一 REST 请求、资源 CRUD、builder 生命周期、Hocuspocus/Yjs 协作连接。 src/remote/http.ts、resource-api.ts、builder-provider.ts、collaboration.ts
chart / insight / report 把通用资源 API 映射成领域 provider,并处理 DSL 与后端 response 之间的转换。 src/chart/*、src/insight/*、src/report/*
agent 面向 Agent 的 workspace slot、默认资源 id、connector registry。 src/agent/kit.ts、workspace.ts、connector-registry.ts
demo connector 内置 demo connector,用 supermarket CSV + VQuery 支持示例查询。 src/demo-connector*.ts、src/dataset/supermarket.csv

Public SDK 接口

入口导出

导出 用途
createVBIProviderClient(config) 主入口,返回 VBIProviderClient。
createVBIProviderAgentKit(options) 创建 { client, workspace },给 agent/CLI 直接打开 chart/report 使用。
createVBIProviderWorkspace({ client, chartId, reportId }) 把 provider 包成可缓存的 workspace slot。
demoConnector / registerDemoConnector() 注册内置 demo 数据源 connector。

Client Options

字段 类型 作用
baseUrl string REST 根路径,例如 http://localhost:3030/api/v1。
fetch RemoteFetch 可选 fetch 实现。缺省使用 globalThis.fetch,Node 旧环境需要注入。
headers Record<string,string> | () => Record REST 请求 headers,支持异步工厂。
syncTimeoutMs number 等待 Hocuspocus 初始同步的超时时间,默认 10000ms。
webSocketPolyfill unknown Node 场景注入 WebSocket polyfill,例如 ws 的 WebSocket。

VBIProviderClient

方法 返回 说明
chart(id?) ChartProvider 可绑定已有 chart id,也可无 id 用于 create-first。
insight(id?) InsightProvider 同上,面向 insight。
report(id?) ReportProvider 同上,面向 report。
listCharts() / listInsights() / listReports() Promise<Summary[]> 分别请求 GET /charts、GET /insights、GET /reports。

Provider 接口矩阵

能力 ChartProvider InsightProvider ReportProvider
资源 id getResourceId() getResourceId() getResourceId()
CRUD create、remove、rename create、remove、rename、update create、remove、rename
打开协作 builder open() / getBuilder() open() / getBuilder() open() / getBuilder()
详情和快照 getDetail()、snapshot() getDetail()、snapshot() getDetail()、snapshot()、exportSnapshot()
引用关系 getReferences() 返回 report/page 引用 getReferences() 返回 report/page 引用 无反向引用接口
Report pages 无 无 createPage、updatePage、removePage、reorderPages

REST 端点契约

Provider SDK 只读取响应里的 data 字段。后端通过全局 interceptor 包装成 { code, message, data },这与 SDK 的 requestRemote 假设一致。

SDK 方法 HTTP 后端来源 返回语义
listCharts / chart.getSummary / chart.getDetail GET /charts、GET /charts/:id ChartController + ChartService summary 或带 dsl 的 chart detail。
chart.create / rename / remove POST /charts、PATCH /charts/:id、DELETE /charts/:id ChartService.create/update/remove 创建后 SDK 会写入 state.resourceId 并再取一次详情来归一化 summary。
chart.getReferences GET /charts/:id/references findReportUsages 返回 { reportId, pageId } 数组。被引用的 chart 删除会 409。
insight.getDetail / insight.update GET /insights/:id、PATCH /insights/:id InsightService 后端返回 content,SDK 映射为 VBIInsightDSL。
report.getDetail / report.exportSnapshot GET /reports/:id、GET /reports/:id/snapshot ReportService.findOne/snapshot 详情返回 pages,SDK 映射成 report DSL;exportSnapshot 会嵌入引用 chart/insight DSL。
report.createPage / updatePage / removePage / reorderPages POST /reports/:id/pages、PATCH /reports/:id/pages/:pageId、DELETE /reports/:id/pages/:pageId、PATCH /reports/:id/pages/reorder ReportService 直接改持久化 report doc;createPage 会同时创建新的 chart 和 insight。
provider.open() GET /{resource}/:id/collaboration + WebSocket getCollaborationSession + HocuspocusServer 先拿 roomName/websocketUrl,再进入 Yjs 文档同步。

数据流逻辑

1. 列表/详情/普通 CRUD

ConsumerBrowser / Node / Agent 调用 SDK
ClientcreateVBIProviderClient 生成 provider
requestRemote拼接 baseUrl + path,注入 headers,解析 data
Nest REST/api/v1 controller + service
Prisma读写 chart/insight/report 二进制 Yjs snapshot

2. 打开 Builder 协作编辑

provider.open复用已打开 builder 或 in-flight promise
GET collaboration拿 roomName 和 websocketUrl
buildSocketUrl根据 chart:ID 等 room 追加 provider path
Hocuspocusattach 后等待 synced=true
Builder用同步后的 Y.Doc 创建 VBI builder

3. 后端协作持久化

  • HocuspocusServer.onLoadDocument 根据 room 前缀 chart:、insight:、report: 找到资源,加载主 snapshot,再按 id 顺序 apply collaboration updates。
  • onChange 把每次 Yjs update 追加到对应的 collaboration update 表。
  • onStoreDocument 把完整 Yjs state 编码后写回主资源表。
  • REST 侧部分变更会直接重建 Yjs doc 并清空 update 表,例如 insight content update、report page 变更。

4. Report snapshot

report.exportSnapshot() 调用 GET /reports/:id/snapshot。后端先从 report pages 读取 chartId 和 insightId,逐个加载引用资源的 Yjs snapshot,转换成 chart/insight DSL,再组装成 { report, charts, insights }。

Agent Workspace

对象 方法 行为
workspace.chart open(id?)、snapshot(id?)、describe(id?)、close(id?) 按资源 id 缓存 provider。打开 chart 后会读取 DSL 里的 connectorId,并尝试确保 connector 已注册。
workspace.report 同 chart slot 只处理 report builder 生命周期,不处理 connector。
workspace.connectors register、registerChart、getChartConnectorId、ensureKnownConnector 目前内置识别 demo connector,其它 connector 需要调用方注册。

Review 发现

P1 REST 直接变更与已打开协作文档可能产生 stale builder

InsightProvider.update() 通过 REST 改后端 doc;如果 builder 已经 open,随后 getDetail() 会走本地 builder,而不是远端新数据。Report 的 createPage/updatePage/removePage/reorderPages 也直接改持久化 doc,但已打开的 report builder 不会被显式刷新。结果是“接口返回的新 detail”和“provider 后续 snapshot/getDetail 的本地 builder 状态”可能分叉。

相关文件:src/insight/remote-provider.ts、src/report/remote-provider.ts、apps/vbi_be/src/insight/insight.service.ts、apps/vbi_be/src/report/report.service.ts

P1 REST headers 没有进入 WebSocket 鉴权链路

Client options 支持 headers,但只用于 REST。协作 WebSocket 创建时只传 url 和可选 polyfill;后端 onAuthenticate 当前固定返回 anonymous。未来只要 REST 加鉴权,协作通道会成为独立的鉴权缺口或不可用点。

相关文件:src/remote/http.ts、src/remote/collaboration.ts、apps/vbi_be/src/app/hocuspocus-server.ts

P2 InsightProvider.snapshot() 冷启动语义和 Chart/Report 不一致

Chart/Report 在未打开 builder 时可通过 REST detail 构造 snapshot;Insight 的 snapshot 直接使用 core.getLocalSnapshot,会强制打开协作连接。如果只是想读 insight 当前 DSL,WebSocket 不可用会导致读操作失败。

相关文件:src/insight/remote-provider.ts

P2 Page 变更 API 与 Builder ownership 边界需要再明确

开发规则里 Builder owns DSL mutation。Provider 现在提供 REST page 变更方法,同时也提供 report builder。如果两条路径都作为一等写路径,需要定义同步策略;否则建议把 page 写入收敛到 builder 协作流,REST 只做资源生命周期和 export。

P2 远程错误和响应 envelope 假设偏窄

requestRemote 成功时直接 response.json() 并取 payload.data,不校验 code/message,也没有处理 204/空响应。当前后端一致返回 envelope,所以能工作;作为 SDK public contract,最好把 envelope 类型和异常格式写入测试。

P3 package metadata 的 repository directory 疑似过期

package.json 里 repository directory 是 apps/packages/vbi-provider,实际路径是 apps/vbi_provider。这不会影响运行,但会影响包元数据和跳转。

测试覆盖现状

已有覆盖

  • Client 创建 provider、列表接口、agent kit 默认 id。
  • Chart CRUD 路由、references、Report snapshot export、page reorder。
  • 协作 open 流程:attach、等待 sync、in-flight open 复用、socket url path。
  • Demo connector 注册和 supermarket CSV 查询。

覆盖数字

当前 coverage-summary.json 显示 total:lines 57.89%,statements 57.51%,functions 39.28%,branches 47.45%。

低覆盖集中在 Insight provider/API、Report provider/API、HTTP error 分支和 provider 本地/远程分支切换。

建议补测

推荐 Review 顺序

  1. 先定 provider 写入模型:REST 写 DSL 还是 builder/Yjs 写 DSL,避免双写路径长期并存。
  2. 再定协作通道鉴权:REST headers、cookie、query token、Hocuspocus authenticate 需要一条完整链路。
  3. 统一三类 provider 的冷读语义:未 open 时 getDetail/snapshot 都应可走 REST,除非明确要求协作连接。
  4. 把 endpoint contract 写进 provider tests:尤其是 envelope、error、page mutation、references、snapshot。
  5. 清理小型元数据问题:repository directory、README 中 REST envelope/WS 约束说明。

源码索引

apps/vbi_provider/README.md

apps/vbi_provider/package.json

apps/vbi_provider/src/index.ts

apps/vbi_provider/src/client.ts

apps/vbi_provider/src/types/*.ts

apps/vbi_provider/src/remote/http.ts

apps/vbi_provider/src/remote/resource-api.ts

apps/vbi_provider/src/remote/builder-provider.ts

apps/vbi_provider/src/remote/collaboration.ts

apps/vbi_provider/src/remote/socket-url.ts

apps/vbi_provider/src/chart/*

apps/vbi_provider/src/insight/*

apps/vbi_provider/src/report/*

apps/vbi_provider/src/agent/*

apps/vbi_provider/tests/*.test.ts

apps/vbi_be/src/chart/*

apps/vbi_be/src/insight/*

apps/vbi_be/src/report/*

apps/vbi_be/src/app/hocuspocus-server.ts

apps/vbi_be/src/common/vbi-doc.ts