TSts-grm

模型

模型(Model)描述一张表的形状。它是整个框架的起点:视图(View)、查询 DSL、保存命令都要引用模型。

// ① 导入:model/prop 建模型,dto 建视图;sqlClient 是示例提供的客户端
import { model, prop, dto } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'

// ② 声明两个模型:BookStore 与 Book(双向关联;顺序无关,见下)
const BookStore = model("BookStore", "id", class {
    id = prop.i64()
    name = prop.str(100)
    books = prop.o2m(Book).mappedBy("store")
})

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()
})

// ③ 同一个模型可被多个视图/查询引用——这里是第二个视图
const StoreView = dto.view(BookStore, c => [
    c.id,
    c.name,
    c.books.with(c => [c.id, c.name, c.edition]),
])

// ④ 用第二个视图查一次:书店 + 其书(集合)
const stores = await sqlClient.createQuery(BookStore, (q, store) => {
    return q.select(store.fetch(StoreView))
}).fetchList()

console.log(stores)

模型是"元数据",不是"对象工厂"

model(name, idKey, class) 做三件事:

  • 登记一个命名实体"BookStore" / "Book"——数据库表的逻辑名);
  • class 的字段初始化器收集属性声明(字段名 → prop.xxx 的定义);
  • 通过可选的第四个参数 ctx 配置表级选项(表名、唯一约束、判别器等,见「继承与多态」)。

做这些事:

  • 不连接数据库;
  • 不实例化任何实体对象——查询结果由视图决定形状,而不是模型;
  • 不触发类型反射——属性声明全部在初始化器里完成,无运行时 introspection。

定义顺序无关(惰性解析)

模型之间的相互引用(Book.storeBookStore.books)可以"先引用后定义":

const A = model("A", "id", class {
    b = prop.m2o(B)
})
const B = model("B", "id", class { ... })

原因是解析是惰性的model(...) 只登记声明;class 真正被实例化(属性初始化)发生在框架第一次解析该模型时——那时模块已经全部加载完毕。唯一的例外是递归自引用(如树节点指向自己),需要显式懒加载:

parent = prop.m2o(() => TREE_NODE)   // 自引用:用 () => 形式

模型与表:命名与 DDL

  • 默认命名策略 UPPER_SNAKE_CASEBookStore → BOOK_STOREstore → STORE_ID(外键列 = 属性名 + _ID);
  • 模型驱动建表:createSchema(SQL 后端章)能从实体管理器汇总的模型生成建表 DDL——声明即 schema;
  • 属性与列是一列一属性的直接映射,无中间层。

下一步

模型由属性构成。下一节把全部属性类型过一遍:标量、可空、枚举、JSON。