- add shared db-app layer for connect/inspect/query/export events - add dbtool-tui workspace with sqlite-local live flow and QA docs - include host-validated acceptance updates for CMP-36 Co-Authored-By: Paperclip <noreply@paperclip.ing>
241 lines
6.3 KiB
Markdown
241 lines
6.3 KiB
Markdown
# TUI Backend Contract
|
||
|
||
日期:2026-03-27
|
||
作者:Senior Backend Engineer
|
||
|
||
## 1. 当前后端边界
|
||
|
||
- `crates/db-core`:数据库无关的领域对象与基础校验。
|
||
- `crates/db-config`:连接 profile、密码环境变量注入与脱敏摘要。
|
||
- `crates/db-drivers`:PostgreSQL / MySQL / SQLite 驱动实现与差异收敛。
|
||
- `crates/db-app`:`connect` / `inspect` / `query` / `export` 共享应用编排、结构化结果、统一错误对象,以及 TUI worker 可复用的执行状态。
|
||
- `apps/cli`:参数解析、stdout/stderr 渲染、退出码。
|
||
|
||
## 2. 持久化边界
|
||
|
||
- 当前产品仍是单次命令执行模型,没有连接配置持久化、历史记录持久化或后台状态存储。
|
||
- 当前写路径只有:
|
||
- `export` 明确指定的输出文件
|
||
- SQLite 目标数据库本身(由用户选择)
|
||
- TUI 第二阶段 live integration 仍不引入新的数据库、本地缓存库或后台 daemon 存储契约。
|
||
|
||
## 3. 共享请求对象
|
||
|
||
TUI worker 应直接构造共享 Rust 对象,不要拼接 CLI flags,更不要解析 CLI 文本。
|
||
|
||
### Connect
|
||
|
||
- 输入:`db_config::ConnectionProfile`
|
||
- 说明:
|
||
- `name` 用于当前会话内的目标标识
|
||
- `target` 复用 `db_core::ConnectionTarget`
|
||
- `password_env_var` 只作为脱敏标签保留;secret 仍通过环境变量注入,不进入持久化
|
||
|
||
### Inspect
|
||
|
||
- 输入:`db_core::InspectRequest`
|
||
- 字段:
|
||
- `schema: Option<String>`
|
||
- `table: Option<String>`
|
||
- 约束:
|
||
- `table.is_some()` 时必须同时提供 `schema`
|
||
|
||
### Query
|
||
|
||
- 输入:`db_core::QueryRequest`
|
||
- 字段:
|
||
- `sql`
|
||
- `source`
|
||
- `parameters`
|
||
- `source` 语义:
|
||
- `QuerySource::Inline`
|
||
- `QuerySource::File`
|
||
|
||
### Export
|
||
|
||
- 输入:
|
||
- `db_core::QueryRequest`
|
||
- `db_core::ExportRequest`
|
||
- `ExportRequest` 字段:
|
||
- `format`
|
||
- `output_path`
|
||
- `overwrite`
|
||
- 约束:
|
||
- 只允许导出返回结果集的查询
|
||
- 不静默覆盖已有文件
|
||
- 不为 TUI 增加新的导出元数据存储
|
||
|
||
## 4. 共享结构化结果
|
||
|
||
TUI 应优先直接调用 `crates/db-app`,不要解析 CLI 的人类可读文本。
|
||
|
||
### Connect
|
||
|
||
- 返回 `ConnectResponse`
|
||
- 字段:
|
||
- `target.profile_name`
|
||
- `target.driver`
|
||
- `target.endpoint`
|
||
- `target.password_env_var`
|
||
- `status`
|
||
- 当前 `status` 固定为 `connected`
|
||
|
||
### Inspect
|
||
|
||
- 返回 `InspectResponse`
|
||
- 字段:
|
||
- `target`
|
||
- `scope.schema`
|
||
- `scope.table`
|
||
- `payload`
|
||
- `payload.kind` 为:
|
||
- `schemas`
|
||
- `tables`
|
||
- `columns`
|
||
- `payload.items` 允许为空;空数组不是错误
|
||
|
||
### Query
|
||
|
||
- 返回 `QueryResponse`
|
||
- 字段:
|
||
- `target`
|
||
- `source.kind`:`inline` / `file`
|
||
- `source.label`
|
||
- `source.path`
|
||
- `result.columns`
|
||
- `result.rows`
|
||
- `result.rows_affected`
|
||
- `result.row_count`
|
||
- `rows_affected != null` 表示 command-style 执行成功但没有结果集
|
||
|
||
### Export
|
||
|
||
- 返回 `ExportResponse`
|
||
- 字段:
|
||
- `target`
|
||
- `source`
|
||
- `export.format`
|
||
- `export.output_path`
|
||
- `export.overwrite`
|
||
- `row_count`
|
||
|
||
## 5. 错误模型
|
||
|
||
`db-app::AppError.kind` 当前分为:
|
||
|
||
- `validation`
|
||
- `connection`
|
||
- `authentication`
|
||
- `inspect`
|
||
- `query`
|
||
- `export`
|
||
|
||
这组分类是 TUI banner、QA 断言和未来 GUI 状态映射的当前稳定基础。
|
||
|
||
`AppError.target` 为可选字段:
|
||
|
||
- 有目标上下文时返回脱敏后的 `ConnectionSummary`
|
||
- 参数校验在目标尚未构成时可为 `null`
|
||
|
||
## 6. Worker / Channel 状态契约
|
||
|
||
`crates/db-app` 现提供:
|
||
|
||
- `AppOperation`:`connect | inspect | query | export`
|
||
- `OperationState`:`running | success | empty | error`
|
||
- `AppEvent<T>`:worker / channel 推荐 envelope
|
||
|
||
推荐发送顺序:
|
||
|
||
1. 先发 `AppEvent::running(operation)`
|
||
2. 成功后发送 `response.into_event()`
|
||
3. 失败时发送 `error.into_event()`
|
||
|
||
状态语义固定如下:
|
||
|
||
- `connect`
|
||
- `running`:正在测试连接
|
||
- `success`:连接成功
|
||
- `error`:连接失败
|
||
- `inspect`
|
||
- `success`:返回非空 schema / table / column 集合
|
||
- `empty`:返回空集合,但请求本身成功
|
||
- `error`:inspect 失败
|
||
- `query`
|
||
- `success`:返回至少一行,或 `rows_affected` 非空
|
||
- `empty`:返回零行且 `rows_affected == null`
|
||
- `error`:执行失败
|
||
- `export`
|
||
- `success`:文件写入成功;`row_count == 0` 仍算成功
|
||
- `error`:导出校验、查询或文件写入失败
|
||
|
||
示例:
|
||
|
||
```rust
|
||
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:
|
||
|
||
```json
|
||
{
|
||
"status": "success",
|
||
"operation": "query",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
错误返回 envelope:
|
||
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"operation": "query",
|
||
"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 语义
|
||
- `ConnectionSummary.endpoint` 与 `password_env_var` 仅用于展示脱敏上下文,不应用作 secret 来源
|
||
- `AppError.kind` 必须原样进入状态区 / inspector,避免把 `validation`、`connection`、`query` 混成统一“失败”
|
||
- 若未来引入异步 worker 池,仍以共享 response/error 对象作为唯一 UI 数据源
|
||
|
||
## 9. 当前回归边界
|
||
|
||
- `crates/db-app` 回归需覆盖:
|
||
- response -> `OperationState` 映射
|
||
- `AppEvent<T>` / `AppError` 序列化
|
||
- 空 inspect / 空 query / command query 语义
|
||
- `apps/cli` 回归继续覆盖:
|
||
- `--result-format json` envelope
|
||
- text/json 输出在相同 `db-app` 结果上的一致性
|
||
|
||
## 10. 演进规则
|
||
|
||
- CLI 文本文案可小幅演进,但 `db-app` 结构化对象字段应保持兼容。
|
||
- 新增数据库驱动时,优先扩展 `db-drivers` 与 `db-app`,不要在 TUI/CLI 内复制数据库语义。
|
||
- 若未来引入本地持久化(例如连接历史),应先新增独立文档定义存储边界,再进入实现。
|