9.7 KiB
9.7 KiB
TUI Backend Contract
日期:2026-04-02
作者:Senior Backend Engineer
1. 当前后端边界
crates/db-core:数据库无关的领域对象与基础校验。crates/db-config:连接 profile、versioned profile store、active profile 语义、session-only secret 缓存、密码环境变量注入与脱敏摘要。crates/db-drivers:PostgreSQL / MySQL / SQLite 驱动实现与差异收敛。crates/db-app:connect/inspect/query/export共享应用编排,以及 profileload/save/delete/activate/resolve共享入口、结构化结果、统一错误对象,与 TUI worker 可复用的执行状态。apps/cli:参数解析、stdout/stderr 渲染、退出码。
2. 持久化边界
- 当前 backend 新增了一条受控本地持久化边界:
db_config::ConnectionProfileStore。 - store 仅持久化:
versionactive_profile- 不含 secret 的
ConnectionProfile[]
- 当前 store 不持久化:
- 明文密码 / session secret
- 查询历史
- 导出历史
- 后台任务状态
- 当前写路径只有:
- 调用方显式传入路径的 profile store JSON 文件
export明确指定的输出文件- SQLite 目标数据库本身(由用户选择)
SessionSecretStore只存在于当前进程内存;重启后不恢复。- 当前阶段仍不引入新的数据库、本地缓存库或后台 daemon 存储契约,也不引入 migration chain。
3. 共享请求对象
TUI worker 应直接构造共享 Rust 对象,不要拼接 CLI flags,更不要解析 CLI 文本。
Connect
- 输入:
db_config::ConnectionProfile - 说明:
name用于当前会话内的目标标识target复用db_core::ConnectionTargetpassword_env_var只作为脱敏标签 / advanced fallback 保留ConnectionTarget.password可在运行态存在,但禁止进入ConnectionProfileCatalog
Connection Management
- profile store:
db_config::ConnectionProfileStore - catalog:
db_config::ConnectionProfileCatalog - session secret cache:
db_config::SessionSecretStore - 保存请求:
db_app::SaveProfileRequestprofileset_activesession_secret:Preserve | Set(String) | Clear
- 共享入口:
db_app::load_profile_catalogdb_app::save_profiledb_app::delete_profiledb_app::activate_profiledb_app::resolve_profiledb_app::resolve_active_profiledb_app::connect_saved_profile
- 约束:
- catalog 内 profile 必须无 secret;若
target.password存在,视为运行态数据,只能通过 session secret 路径保留 active_profile必须指向已存在 profile,或为null- 同名 profile 视为 upsert,不引入第二份记录
- catalog 内 profile 必须无 secret;若
Inspect
- 输入:
db_core::InspectRequest - 字段:
schema: Option<String>table: Option<String>
- 约束:
table.is_some()时必须同时提供schema
Query
- 输入:
db_core::QueryRequest - 字段:
sqlsourceparameters
source语义:QuerySource::InlineQuerySource::File
Export
- 输入:
db_core::QueryRequestdb_core::ExportRequest
ExportRequest字段:formatoutput_pathoverwrite
- 约束:
- 只允许导出返回结果集的查询
- 不静默覆盖已有文件
- 不为 TUI 增加新的导出元数据存储
4. 共享结构化结果
TUI 应优先直接调用 crates/db-app,不要解析 CLI 的人类可读文本。
Connect
- 返回
ConnectResponse - 字段:
target.profile_nametarget.drivertarget.endpointtarget.password_env_varstatus
- 当前
status固定为connected
Connection Management
load_profile_catalog/save_profile/delete_profile/activate_profile返回ProfileCatalogResponse- 字段:
active_profile_nameprofiles[]targetis_activesecret_source:none | session | env_var
resolve_profile/resolve_active_profile返回运行态ConnectionProfile- 若 session secret 存在,优先注入该 secret
- 否则再尝试
password_env_var - 解析阶段仍不把 secret 暴露到 summary / doc / 持久化内容
Inspect
- 返回
InspectResponse - 字段:
targetscope.schemascope.tablepayload
payload.kind为:schemastablescolumns
- 当
payload.kind=schemas时,每个 schema item 额外包含:nameavailability:ready | restrictednote:可选;当availability=restricted时给出权限说明
payload.items允许为空;空数组不是错误- PostgreSQL root inspect 现会保留 restricted schema 名称,并通过 item-level
availability暴露权限状态 - 当显式 schema inspect 命中存在但当前连接无权访问的 PostgreSQL schema 时,返回
AppError.kind=inspect,不再把 restricted schema 误报为 empty
Query
- 返回
QueryResponse - 字段:
targetsource.kind:inline/filesource.labelsource.pathresult.columnsresult.rowsresult.rows_affectedresult.row_count
rows_affected != null表示 command-style 执行成功但没有结果集
Export
- 返回
ExportResponse - 字段:
targetsourceexport.formatexport.output_pathexport.overwriterow_count
5. 错误模型
db-app::AppError.kind 当前分为:
validationconnectionauthenticationinspectqueryexportconfig
这组分类是 TUI banner、QA 断言和未来 GUI 状态映射的当前稳定基础。
AppError.target 为可选字段:
- 有目标上下文时返回脱敏后的
ConnectionSummary - 参数校验在目标尚未构成时可为
null
6. Worker / Channel 状态契约
crates/db-app 现提供:
AppOperation:connect | inspect | query | export | load_profiles | save_profile | delete_profile | activate_profileOperationState:running | success | empty | errorAppEvent<T>:worker / channel 推荐 envelope
推荐发送顺序:
- 先发
AppEvent::running(operation) - 成功后发送
response.into_event() - 失败时发送
error.into_event()
状态语义固定如下:
connectrunning:正在测试连接success:连接成功error:连接失败
inspectsuccess:返回非空 schema / table / column 集合empty:返回空集合,但请求本身成功error:inspect 失败;包括 restricted schema 等显式 scope 无权访问场景
querysuccess:返回至少一行,或rows_affected非空empty:返回零行且rows_affected == nullerror:执行失败
exportsuccess:文件写入成功;row_count == 0仍算成功error:导出校验、查询或文件写入失败
load_profiles | save_profile | delete_profile | activate_profile- 当前先作为共享 operation 名称与 error.operation 保留,供连接管理 worker 接入
- 若后续 channelize 这些动作,应继续沿用
running | success | error,不要另起一套状态词
示例:
use db_app::{AppEvent, AppOperation};
sender.send(AppEvent::<()>::running(AppOperation::Query))?;
match db_app::query(&profile, &request) {
Ok(response) => sender.send(response.into_event())?,
Err(error) => sender.send(error.into_event())?,
}
7. CLI 结构化输出
CLI 继续保留默认文本输出,同时新增:
--result-format text--result-format json
JSON 返回 envelope:
{
"status": "success",
"operation": "query",
"state": "success",
"data": {}
}
status继续只区分 envelope 级成功/失败state对齐共享应用层状态:connect:successinspect:success | emptyquery:success | emptyexport:success
错误返回 envelope:
{
"status": "error",
"operation": "query",
"state": "error",
"error": {
"operation": "query",
"kind": "query",
"message": "..."
}
}
CLI JSON envelope 继续作为跨入口回归样本,但 TUI live path 不应以调用 CLI 二进制作为主集成方式。
8. Frontend 接入约束
- TUI 直接依赖
crates/db-app与db-core,不解析 CLI stdout/stderr - UI 状态映射应优先使用
OperationState,不要自行发明另一套 success/empty/error 语义 - UI 在 schema 根列表上应优先消费 item-level
availability/note,不要把 restricted schema 再降级成 empty ConnectionSummary.endpoint与password_env_var仅用于展示脱敏上下文,不应用作 secret 来源- 连接管理 UI 应优先消费
ProfileCatalogResponse.secret_source与 active profile 语义,不自行读取持久化文件 AppError.kind必须原样进入状态区 / inspector,避免把validation、connection、query混成统一“失败”- 若未来引入异步 worker 池,仍以共享 response/error 对象作为唯一 UI 数据源
9. 当前回归边界
crates/db-app回归需覆盖:- response ->
OperationState映射 AppEvent<T>/AppError序列化- 空 inspect / 空 query / command query 语义
- profile save/load/activate 的 secret 非持久化边界
- response ->
apps/cli回归继续覆盖:--result-format jsonenvelope- text/json 输出在相同
db-app结果上的一致性
10. 演进规则
- CLI 文本文案可小幅演进,但
db-app结构化对象字段应保持兼容。 - 新增数据库驱动时,优先扩展
db-drivers与db-app,不要在 TUI/CLI 内复制数据库语义。 - 若未来扩展 profile store schema(例如新增 profile id、secret provider、连接历史),必须先 bump version 并补迁移策略文档,再进入实现。