# 自定义数据类型

> 前置：模型的属性声明请先看 [「属性与类型」](/docs/core-concepts/props)（本页是其「自定义数据类型」专述）。

内置标量（i64/str/num/…）覆盖多数列。当列的真实语义是“更高级的类型”时（二进制、特定编码、图片对象、自定义枚举存储），用 `ScalarProvider` 定义**自定义数据类型**。

## 结构：值类型 + SQL 类型 + 双向转换

```ts
import { ScalarProvider, ScalarType, prop, scalars } from '@ts-grm/core'
import { z } from 'zod'

const binaryProvider = ScalarProvider.of({
    valueType: z.instanceof(Uint8Array),   // 值的 TypeScript 语义（StandardSchemaV1）
    sqlType: ScalarType.binary(1024),      // 底层 SQL 类型（BINARY(1024)）
    toValue: (sqlValue) => sqlValue,       // 数据库值 → 应用值
    toSql: (value) => value,               // 应用值 → 数据库值
})

// 挂到模型属性
image = prop.scalar(binaryProvider)
```

三个要求：

- `valueType` 必须**不含 null/undefined**（可空用 `.nullable()` 表达）；
- `toValue / toSql` 负责数据库 ↔ 应用的形状转换；
- 结果：属性在 TS 里是 `z.instanceof(...)` 推导出的类型，库里仍按 `sqlType` 存取。

## json / jsonb 就是它的实例

`prop.json(schema)` / `prop.jsonb(schema)` 直接接收**任何 Standard Schema**（`zod` 即标准 schema）：`valueType` 里 `toValue/toSql` 做序列化，`sqlType` 固定 JSON/JSONB。

> 底层 `scalars.jsonProvider(valueType)` / `jsonbProvider(valueType)` 返回 `ScalarProvider`（供 Driver/自定义列类型用）——**给 `prop.json` 用不需要包它**，直接传 schema 即可：

```ts workspace
// ① 模型（完整可运行版见工作区）
import { model, prop, dto } from '@ts-grm/core'
import { z } from 'zod'
import { sqlClient } from './infra/sql-client'

const Book = model("Book", "id", class {
    id = prop.i64()
    name = prop.str(50)
    edition = prop.i32()
    price = prop.num(10, 2)
    // 自定义数据类型：JSONB 存结构化内容（zod 描述形状 → prop.json 直接收）
    meta = prop.json(z.object({
        isbn: z.string(),
        rating: z.number().min(1).max(5),
    }))
})

// ② 查询普通列（meta 列在沙盒种子中不存在，不放进视图即可）
const rows = await sqlClient.createQuery(Book, (q, b) => {
    return q.select(b.fetch(dto.view(Book, c => [c.id, c.name])))
}).fetchList()

console.log(rows)
```

## ts-grm 自带的 StandardSchemaV1

`prop.enum` / `prop.enumSet` 内部会构造 **vendor: 'ts-grm'** 的 StandardSchemaV1 实现，把"值必须 ∈ 集合"表达成 schema 校验——框架的 Nullity 判断等逻辑也会消费它。含义：

- 值类型体系与生态互通（zod、valibot 等 StandardSchema 兼容库都能作 `valueType`）；
- 自定义 provider 不必自己实现校验，用现成 zod 即可。

## 应用场景示例

- **数据库 BINARY → 应用端 Uint8Array / base64 字符串**：转换集中在 provider，业务层拿到的就是理想类型；
- **位掩码/枚举编码列**：`number` 存、位运算后解成"特性集合"；
- **JSONB 与嵌套结构**：zod 定义结构，库侧 JSONB，读写自动转换。

## 边界与提醒

- provider 参与 `$allScalars`、排序与取形——形状转换以 `toValue` 为准；
- 列宽/类型必须与 `sqlType` 匹配（`createSchema` 的 DDL 也按它生成）；
- 性能敏感列（大字段）避免过重转换逻辑。
