输入映射与校验(mapInput / key())
Input DTO 除了选字段,还能在类型层面外给载荷加「门口校验 + 值映射」。机制是标量/embedded 映射上的
mapInput(schema, mapper):先按一个 Standard Schema V1 校验原始输入,再通过 mapper 转成模型属性的真实值。
Standard Schema 是跨校验库的通用接口约定——任何实现了该规范的库(zod、valibot 等)都能传进来。
mapInput:先校验、再映射
典型场景:接口层拿到的是「用户友好」的输入,存库前需要校验 + 规整。
// ① mapInput:价格列接受字符串输入,校验后转 number 落库
// import { model, prop, dto, scalars } from '@ts-grm/core'
// const Book = model("Book", "id", class {
// id = prop.i64()
// name = prop.str(50)
// price = prop.num(10, 2)
// })
//
// const BookInput = dto.input(Book, c => [
// c.id.key(),
// c.name,
// c.price.mapInput(
// // schema:符合 Standard Schema V1 的校验器(zod/valibot 皆可)
// z.string().regex(/^\d+(\.\d{1,2})?$/),
// // mapper:把校验通过的输入转成模型列类型
// s => Number(s),
// ),
// ])
mapInput存在于完整输入映射上,与.key()同层可组合;- 载荷类型随之变化:
TypeOf<BookInput>里price变成校验器的输出类型(此例为string)——接口层的「用户输入形」与「库内存储形」就此分开,由 mapper 衔接; - 校验失败即整体保存失败——「门口拦下」,而不是写一半再回滚。
key():定位依据(重述)
.key() 标记保存键,是 UPSERT / INSERT_IF_ABSENT / UPDATE 的定位依据;标量与 embedded 都可标记(见「根保存模式」):
// ② key 组合:name + edition 作为业务唯一键
// const BookInput = dto.input(Book, c => [
// c.name.key(),
// c.edition.key(),
// c.price,
// ])
as():载荷字段重命名
输入字段可用 .as(alias) 改名——载荷里用乙方喜欢的名字,落库仍是模型列名:
// ③ as():载荷侧改名
// const BookInput = dto.input(Book, c => [
// c.id.key(),
// c.name.as("title"), // 接收端写 title,存库仍是 name
// ])
// // TypeOf<BookInput> ≈ { id: bigint; title: string }
组合示例:一个「防呆」的创建接口
// ④ 组合:key + as + mapInput(设计形态)
// const CreateBookInput = dto.input(Book, c => [
// c.name.as("title").key(),
// c.edition,
// c.price.mapInput(z.coerce.number().positive(), s => s),
// ])
// // 载荷:{ title: string; edition: number; price: number }
// // 幂等叠加:title 定位(UPSERT)、price 走校验
小结
mapInput(schema, mapper):Standard Schema 门口校验 + 值映射,让载荷与库列类型解耦;key():保存键定位(UPSERT 等依赖它);as():载荷侧别名。