# 模型

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

```ts workspace
// ① 导入：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.store` ↔ `BookStore.books`）可以"先引用后定义"：

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

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

```ts
parent = prop.m2o(() => TREE_NODE)   // 自引用：用 () => 形式
```

## 模型与表：命名与 DDL

- 默认命名策略 `UPPER_SNAKE_CASE`：`BookStore → BOOK_STORE`、`store → STORE_ID`（外键列 = 属性名 + `_ID`）；
- 模型驱动建表：`createSchema`（SQL 后端章）能从实体管理器汇总的模型生成建表 DDL——声明即 schema；
- 属性与列是**一列一属性**的直接映射，无中间层。

## 下一步

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