# 事务

用 `sqlClient.execute(...)` 把一段工作包进**事务**。四种调用形态：

```ts
await sqlClient.execute(async () => { ... })              // 默认（REQUIRED）
await sqlClient.execute("REQUIRES_NEW", async () => { ... }) // 传播级别
await sqlClient.execute("SERIALIZABLE", async () => { ... }) // 隔离级别
await sqlClient.execute({ propagation, isolation, timeout }, async () => { ... }) // 全选项
```

事务块里返回的值会原样透出（`execute` 返回 `fn` 的返回值），异常时回滚。

## 传播级别（Propagation）

| 值 | 语义 |
|---|---|
| `REQUIRED`（默认） | 已有事务则加入，没有则新建 |
| `REQUIRES_NEW` | 总是挂起外层、开一个新事务 |
| `NESTED` | 嵌套事务（保存点） |
| `NOT_SUPPORTED` | 挂起外层事务、无事务执行 |
| `NEVER` | 已有事务则抛错 |
| `MANDATORY` | 必须已有事务，否则抛错 |

## 隔离级别（Isolation）

`READ_UNCOMMITTED` / `READ_COMMITTED`（PG 默认）/ `REPEATABLE_READ` / `SERIALIZABLE`——语义与数据库标准一致。

## 沙盒里直接可用

示例中的 `sqlClient` 已带事务管理器，事务内跑查询完全没问题：

```ts workspace
// ① 模型（完整可运行版见工作区）
import { model, prop, dto } from '@ts-grm/core'
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)
})

const BookView = dto.view(Book, c => [c.id, c.name, c.price])

// ② 默认事务：把一组查询放进同一个事务
const rows = await sqlClient.execute(async () => {
    const a = await sqlClient.createQuery(Book, (q, b) => q.select(b.fetch(BookView))).fetchList()
    const n = await sqlClient.createQuery(Book, (q, b) => {
        q.where(b.price.gt(50))
        return q.select(b.fetch(BookView))
    }).fetchCount()
    return { all: a.length, expensive: n }
})

console.log(rows)   // { all: 3, expensive: 2 }

// ③ 显式选项：新事务 + 可重复读 + 超时（毫秒）
const isolated = await sqlClient.execute({
    propagation: "REQUIRES_NEW",
    isolation: "REPEATABLE_READ",
    timeout: 5000,   // 毫秒
}, async () => {
    return (await sqlClient.createQuery(Book, (q, b) => q.select(b.fetch(BookView))).fetchList()).length
})

console.log('REQUIRES_NEW:', isolated)
```

## 注意事项

- **只读事务也是事务**：变更 API（保存/删除）尚未发布，事务内目前以读为主；未来 `save` 落地后同一 `execute` 包裹即拿到原子性；
- 事务块内再次 `execute` 按传播级别处理（默认 REQUIRED = 并入外层）；
- 连接来自驱动连接池，事务按需获取/释放，无需手工 begin/commit。
