feat(usable): integrate current dbtool implementation snapshot
Some checks failed
release-smoke / macos-13 / x86_64-apple-darwin (push) Has been cancelled
release-smoke / ubuntu-latest / x86_64-unknown-linux-gnu (push) Has been cancelled
release-smoke / windows-latest / x86_64-pc-windows-msvc (push) Has been cancelled

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Paperclip CTO
2026-04-02 08:26:18 +00:00
parent a28dab4cd9
commit d5f69462b0
73 changed files with 5895 additions and 322 deletions

View File

@@ -1,23 +1,34 @@
# TUI Backend Contract
日期2026-03-27
日期2026-04-02
作者Senior Backend Engineer
## 1. 当前后端边界
- `crates/db-core`:数据库无关的领域对象与基础校验。
- `crates/db-config`:连接 profile、密码环境变量注入与脱敏摘要。
- `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` 共享应用编排、结构化结果、统一错误对象,以及 TUI worker 可复用的执行状态。
- `crates/db-app``connect` / `inspect` / `query` / `export` 共享应用编排,以及 profile `load` / `save` / `delete` / `activate` / `resolve` 共享入口、结构化结果、统一错误对象, TUI worker 可复用的执行状态。
- `apps/cli`参数解析、stdout/stderr 渲染、退出码。
## 2. 持久化边界
- 当前产品仍是单次命令执行模型,没有连接配置持久化、历史记录持久化或后台状态存储
- 当前 backend 新增了一条受控本地持久化边界:`db_config::ConnectionProfileStore`
- store 仅持久化:
- `version`
- `active_profile`
- 不含 secret 的 `ConnectionProfile[]`
- 当前 store 不持久化:
- 明文密码 / session secret
- 查询历史
- 导出历史
- 后台任务状态
- 当前写路径只有:
- 调用方显式传入路径的 profile store JSON 文件
- `export` 明确指定的输出文件
- SQLite 目标数据库本身(由用户选择)
- TUI 第二阶段 live integration 仍不引入新的数据库、本地缓存库或后台 daemon 存储契约
- `SessionSecretStore` 只存在于当前进程内存;重启后不恢复
- 当前阶段仍不引入新的数据库、本地缓存库或后台 daemon 存储契约,也不引入 migration chain。
## 3. 共享请求对象
@@ -29,7 +40,30 @@ TUI worker 应直接构造共享 Rust 对象,不要拼接 CLI flags更不
- 说明:
- `name` 用于当前会话内的目标标识
- `target` 复用 `db_core::ConnectionTarget`
- `password_env_var` 只作为脱敏标签保留secret 仍通过环境变量注入,不进入持久化
- `password_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::SaveProfileRequest`
- `profile`
- `set_active`
- `session_secret``Preserve | Set(String) | Clear`
- 共享入口:
- `db_app::load_profile_catalog`
- `db_app::save_profile`
- `db_app::delete_profile`
- `db_app::activate_profile`
- `db_app::resolve_profile`
- `db_app::resolve_active_profile`
- `db_app::connect_saved_profile`
- 约束:
- catalog 内 profile 必须无 secret`target.password` 存在,视为运行态数据,只能通过 session secret 路径保留
- `active_profile` 必须指向已存在 profile或为 `null`
- 同名 profile 视为 upsert不引入第二份记录
### Inspect
@@ -80,6 +114,20 @@ TUI 应优先直接调用 `crates/db-app`,不要解析 CLI 的人类可读文
- `status`
- 当前 `status` 固定为 `connected`
### Connection Management
- `load_profile_catalog` / `save_profile` / `delete_profile` / `activate_profile` 返回 `ProfileCatalogResponse`
- 字段:
- `active_profile_name`
- `profiles[]`
- `target`
- `is_active`
- `secret_source``none | session | env_var`
- `resolve_profile` / `resolve_active_profile` 返回运行态 `ConnectionProfile`
- 若 session secret 存在,优先注入该 secret
- 否则再尝试 `password_env_var`
- 解析阶段仍不把 secret 暴露到 summary / doc / 持久化内容
### Inspect
- 返回 `InspectResponse`
@@ -92,7 +140,13 @@ TUI 应优先直接调用 `crates/db-app`,不要解析 CLI 的人类可读文
- `schemas`
- `tables`
- `columns`
-`payload.kind=schemas` 时,每个 schema item 额外包含:
- `name`
- `availability``ready | restricted`
- `note`:可选;当 `availability=restricted` 时给出权限说明
- `payload.items` 允许为空;空数组不是错误
- PostgreSQL root inspect 现会保留 restricted schema 名称,并通过 item-level `availability` 暴露权限状态
- 当显式 schema inspect 命中存在但当前连接无权访问的 PostgreSQL schema 时,返回 `AppError.kind=inspect`,不再把 restricted schema 误报为 empty
### Query
@@ -129,6 +183,7 @@ TUI 应优先直接调用 `crates/db-app`,不要解析 CLI 的人类可读文
- `inspect`
- `query`
- `export`
- `config`
这组分类是 TUI banner、QA 断言和未来 GUI 状态映射的当前稳定基础。
@@ -141,7 +196,7 @@ TUI 应优先直接调用 `crates/db-app`,不要解析 CLI 的人类可读文
`crates/db-app` 现提供:
- `AppOperation``connect | inspect | query | export`
- `AppOperation``connect | inspect | query | export | load_profiles | save_profile | delete_profile | activate_profile`
- `OperationState``running | success | empty | error`
- `AppEvent<T>`worker / channel 推荐 envelope
@@ -160,7 +215,7 @@ TUI 应优先直接调用 `crates/db-app`,不要解析 CLI 的人类可读文
- `inspect`
- `success`:返回非空 schema / table / column 集合
- `empty`:返回空集合,但请求本身成功
- `error`inspect 失败
- `error`inspect 失败;包括 restricted schema 等显式 scope 无权访问场景
- `query`
- `success`:返回至少一行,或 `rows_affected` 非空
- `empty`:返回零行且 `rows_affected == null`
@@ -168,6 +223,9 @@ TUI 应优先直接调用 `crates/db-app`,不要解析 CLI 的人类可读文
- `export`
- `success`:文件写入成功;`row_count == 0` 仍算成功
- `error`:导出校验、查询或文件写入失败
- `load_profiles | save_profile | delete_profile | activate_profile`
- 当前先作为共享 operation 名称与 error.operation 保留,供连接管理 worker 接入
- 若后续 channelize 这些动作,应继续沿用 `running | success | error`,不要另起一套状态词
示例:
@@ -228,7 +286,9 @@ CLI JSON envelope 继续作为跨入口回归样本,但 TUI live path 不应
- 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 数据源
@@ -238,6 +298,7 @@ CLI JSON envelope 继续作为跨入口回归样本,但 TUI live path 不应
- response -> `OperationState` 映射
- `AppEvent<T>` / `AppError` 序列化
- 空 inspect / 空 query / command query 语义
- profile save/load/activate 的 secret 非持久化边界
- `apps/cli` 回归继续覆盖:
- `--result-format json` envelope
- text/json 输出在相同 `db-app` 结果上的一致性
@@ -246,4 +307,4 @@ CLI JSON envelope 继续作为跨入口回归样本,但 TUI live path 不应
- CLI 文本文案可小幅演进,但 `db-app` 结构化对象字段应保持兼容。
- 新增数据库驱动时,优先扩展 `db-drivers``db-app`,不要在 TUI/CLI 内复制数据库语义。
- 若未来引入本地持久化(例如连接历史),应先新增独立文档定义存储边界,再进入实现。
- 若未来扩展 profile store schema例如新增 profile id、secret provider、连接历史必须先 bump version 并补迁移策略文档,再进入实现。