模型
模型描述一张表的形状。用 model(name, idKey, class) 声明,class 里的字段就是数据库列:
// ① 导入:model/prop 建模型,dto 建视图;sqlClient 是示例提供的客户端
import { model, prop, dto } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'
// ② 声明书店模型(注意:此处先引用了后面才定义的 Book——模型解析是惰性的,顺序无关)
const BookStore = model("BookStore", "id", class {
id = prop.i64()
name = prop.str(100)
books = prop.o2m(Book).mappedBy("store") // 反向集合:纯逻辑,不产生列;“store”是 Book 上的真实属性名
})
// ③ 声明书模型:store 是“拥有方”——外键列 store_id 由它生成
const Book = model("Book", "id", class {
id = prop.i64()
name = prop.str(50)
edition = prop.i32()
price = prop.num(10, 2)
store = prop.m2o(BookStore).nullable() // 多对一:外键列按命名策略生成(本沙盒 → STORE_ID);nullable = 可无书店
})
// ④ 查询:每本书带上它的书店(嵌套取形 c.store.with)
const rows = await sqlClient.createQuery(Book, (q, book) => {
return q.select(book.fetch(
dto.view(Book, c => [
c.$allScalars, // 全部标量列
c.store.with(c => [c.id, c.name]), // 并取关联的书店(id, name)
])
))
}).fetchList()
console.log(rows)
要点:
- 第二个参数是主键属性名(
"id");复合主键用prop.embedded({...})(「核心概念 → 复合值」); - 字段名即列名:默认命名策略下自动映射为大写蛇形(
UPPER_SNAKE_CASE:name → NAME、store → STORE_ID),可自定义策略(「SQL 后端 → 命名策略」); - 模型只是元数据:
model(...)不实例化任何对象、不连数据库——查询结果的形状由视图决定; - 关联是属性:
m2o生成外键列(拥有方);一对一o2o、一对多o2m、多对多m2m在「核心概念 → 关联」展开; - 反向关联用
mappedBy:BookStore.books = prop.o2m(Book).mappedBy("store")是纯逻辑反向——不产生列、不建外键,"store"是Book上真实外键属性的名字; - 定义顺序无关:模型解析是惰性的,
BookStore先引用后面才定义的Book完全合法(自引用用() => 模型形式,见「核心概念 → 关联」)。
属性类型
| 声明 | 语义 | PostgreSQL 对应 |
|---|---|---|
prop.i8() / i16() / i32() / i64() | 有符号整数 | SMALLINT ~ BIGINT |
prop.num(p, s) | 定点数 | NUMERIC(p, s) |
prop.f32() / f64() | 浮点 | FLOAT / DOUBLE PRECISION |
prop.str(n) | 定长字符串 | VARCHAR(n) |
prop.text() | 长文本 | TEXT |
prop.bool() | 布尔 | BOOLEAN |
prop.dt() | 日期时间 | TIMESTAMP |
prop.json(schema) | 带结构校验的 JSON | JSON / JSONB(prop.jsonb) |
prop.enum({...}) / enumSet(...) | 枚举 / 枚举集合 | 字符串列 |
prop.embedded({...}) | 复合结构(可作复合键) | 多列展开 |
两个容易踩的细节:
id = prop.i64() // number
id = prop.i64().asString() // string —— 超出 Number.MAX_SAFE_INTEGER 时用它
- 所有类型默认非空,加
.nullable()允许NULL; i64().asString()是 ts-grm 的既定用法:读写时自动在string与数据库BIGINT间转换。
下一步
模型回答了"表里有什么"。"这次查询拿什么回来"由视图回答——下一节声明第一个视图。