关于契约生成
1. 核心策略:以数据库为底薪,按需“裁剪”
你不需要凭空设计返回结构。既然你有了 Drizzle 定义的数据库表,它就是你的数据原形。
-
默认状态:直接返回数据库的整个 Row(使用
createSelectSchema)。 -
需要精简时:用
t.Pick或t.Omit删掉敏感字段(如密码、密钥)。 -
需要关联时:用
t.Object叠加。
2. 解决“不知道返回什么”:三个典型的契约模板
你可以把常见的返回需求归纳为这三种“模版”,AI 会根据这些模版帮你补全逻辑。
A. 列表页(通用模版)
如果你不知道列表要返回什么,就固定返回“实体列表 + 总数”。
TypeScript
// 以后所有列表接口都复用这个结构
export const CreateListResponse = <T extends TSchema>(itemSchema: T) =>
t.Object({
data: t.Array(itemSchema),
meta: t.Object({
total: t.Number(),
page: t.Number(),
pageSize: t.Number()
})
});
// 使用时:
export const UserListResponse = CreateListResponse(UserSelectSchema);
B. 详情页(数据库原形 + 关联)
当你不知道详情页要返回什么,先返回“数据库全字段”,等写到业务发现需要关联数据时,再回来加一个字段。
TypeScript
export const UserDetailResponse = t.Object({
...UserSelectSchema.properties,
// 还不确定关联什么?先留个 Any 或者可选,写业务时再改
roles: t.Optional(t.Array(t.String())),
});
C. 操作结果(极简模版)
对于增删改,如果你不知道返回什么,统一返回一个“状态 + ID”。
TypeScript
export const MutationResponse = t.Object({
success: t.Boolean(),
id: t.Optional(t.String()),
message: t.Optional(t.String())
});
3. 工作流:先“抄”数据库,后“改”契约
不要尝试一步到位,你可以尝试这种**“渐进式契约开发”**:
-
第一步(粗排):后端直接用 Drizzle 的
SelectSchema作为返回契约。 -
第二步(开发):前端开始写页面,发现页面上需要展示一个“所属部门名称”,但数据库里只有
departmentId。 -
第三步(修正):回到
packages/contract,在对应的 Response Schema 里加一行deptName: t.String()。 -
第四步(对齐):此时后端报错(因为没返回这个字段),前端也报错(因为没处理这个字段)。两边一改,契约达成。
4. 自动化:让脚本帮你“写”初稿
如果你觉得定义每个接口太累,我可以帮你写一个**“契约初稿生成器”**。
这个脚本可以扫描你的 Drizzle Table,自动为每个表生成:
-
一个
Create输入 (Omit ID) -
一个
List响应 (Array of Table) -
一个
Update输入 (Partial)
这样你只需要在生成的初稿上删减,而不是从零开始写。
5. 总结:如何克服“定义每个接口”的恐惧?
-
不要怕改:契约不是写死在石头上的,它是可以随业务流转的 TS 代码。
-
重用大于定义:建立自己的
helper/query-types.t.model.ts,把分页、基础响应包装好。 -
AI 辅助:当你定义了
UserTable,你告诉 AI:“基于这个表,给我写一个 Elysia 的增删改查契约”,它生成的准确率极高。
4. 命名与分界点的终极总结
为了让 AI 彻底不迷路,我建议你在生成的契约里遵循以下分界命名法:
-
Schema 对象 (用于后端校验):
-
XxxContract.Create-> 后端body校验。 -
XxxContract.ListQuery-> 后端query校验。
-
-
Static 类型 (用于前端定义):
-
XxxDTO['Create']-> 前端表单的useState类型。 -
XxxDTO['Response']-> 前端列表或详情的渲染类型。
-