TSts-grm

输入映射与校验(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():载荷侧别名。