根保存模式(root)
sqlClient.save(input, payload, options) 的第一个选项是 root,决定根对象(Input 声明所挂的那个模型)如何落库。五个值全部是字符串字面量联合:
// ① RootSaveMode:当前 API 设计
import type { RootSaveMode } from '@ts-grm/core'
// type RootSaveMode =
// | "UPSERT" // 存在则更新、不存在则插入(幂等)
// | "INSERT" // 无条件插入
// | "INSERT_IF_ABSENT" // 按 key 判断,不存在才插入
// | "UPDATE" // 按 key 定位并更新
// | "NON_IDEMPOTENT_UPSERT" // 非幂等 UPSERT
⚠️ 设计预览:以上为 API 名称所表达的语义, 具体边界(如 UPDATE 遇到不存在的 key)待上游发布后以实际行为为准。
一个输入、五种模式
以「书」为例,输入只给更新所需的字段,key 标记 id:
// ② 输入 DTO:id 标记为保存键,其余为可写字段
import { model, prop, dto } from '@ts-grm/core'
import type { TypeOf } from '@ts-grm/core'
const Book = model("Book", "id", class {
id = prop.i64()
name = prop.str(50)
edition = prop.i32()
price = prop.num(10, 2)
})
const BookInput = dto.input(Book, c => [
c.id.key(), // 保存键:UPSERT/UPDATE 用它定位行
c.name,
c.price,
])
// ③ 同一输入 × 不同 root(设计形态)
// const payload: TypeOf<BookInput> = { id: 99n, name: "…", price: 50 }
// await sqlClient.save(BookInput, payload, { root: "INSERT" }) // 无条件新增
// await sqlClient.save(BookInput, payload, { root: "INSERT_IF_ABSENT" }) // id=99 不存在才插入
// await sqlClient.save(BookInput, payload, { root: "UPDATE" }) // 按 id=99 更新
// await sqlClient.save(BookInput, payload, { root: "UPSERT" }) // 有则更、无则增
// await sqlClient.save(BookInput, payload, { root: "NON_IDEMPOTENT_UPSERT" })
差异集中在幂等性与key 的参与方式:
- INSERT:不关心 key,直接插入——重复提交会产生重复行;
- INSERT_IF_ABSENT:先按 key 探测,不存在才插入(「有则跳过」);
- UPDATE:按 key 定位并更新,不存在的 key 不产生插入;
- UPSERT:按 key 合并——存在走更新、不存在走插入,可安全重放(幂等);
- NON_IDEMPOTENT_UPSERT:语义同 UPSERT,但每次执行都实际写(不因数据未变化而跳过)。
key():定位依据不只主键
key() 不限于 id。标量与 embedded 都可以标记,
按业务唯一键定位是常见用法——例如书的「名称 + 版次」在书店语境里唯一:
// ④ key() 支持业务唯一键:name + edition 作为定位依据
const BookInputByKey = dto.input(Book, c => [
c.name.key(),
c.edition.key(),
c.price,
])
// save(BookInputByKey, { name: "TypeScript in Depth", edition: 2, price: 79 },
// { root: "UPSERT" }) // 命中 (name, edition) → 更新价;未命中 → 插入
肉眼可辨识的收益:「存在并更新、不存在则创建」不再需要先查一次——把 UPSERT + key 交给 save,由一条语句完成。
与关联载荷的关系
root 只管根对象那一行。输入里内嵌的关联(.with() / $ref() 覆盖到的部分)由第二档选项
associated 按路径逐个约束——见下一页「关联保存模式」。