TSts-grm

为什么这么快:TS7 原生编译器

先回答一个更容易被忽略的问题:ts-grm 为什么那么依赖编译器性能?

类型体操是特性,也是成本

ts-grm 的设计核心是「零代码生成」:不跑任何预处理器,模型、视图、查询链、结果形状全部由 TypeScript 的类型系统在编译期推导。这套类型系统非常——为了做到

  • model(...) 只写一次元数据,字段类型与可空性从 class 定义推断;
  • dto.view(...) 让查询结果的 DTO 形状精确到列;
  • 查询链 q.where(...).orderBy(...).select(...)编译期校验列存在、类型匹配、取形闭合;
  • 关联、多态、组合键等高级取形都有对应的类型级约束;

框架内部铺了大量条件类型、泛型约束与重载。后果是:同样的「业务代码量」,ts-grm 的类型检查负担比普通 ORM 高一个数量级。在老的 JS 编译器上,这是 IDE 里肉眼可见的卡顿;在 TS7 原生编译器上,它变成即时反馈。

这正是 ts-grm 上游 package.json 把 TypeScript 直接钉在 ^7.0.2 的原因。

TypeScript 7 换了一台发动机

TypeScript 7 不再是「JS 写的编译器 + 自举」——核心编译管线用原生语言重写,tsc 与语言服务(tsserver)都是原生二进制。对类型体操密集的代码,收益主要体现在两处:

  • 类型计算本身大幅加速(条件类型展开、泛型实例化不再经过 JS 解释);
  • 冷启动大幅下降(不再需要加载几百 KB 的 JS 编译器再暖机)。

类型检查实测

下面是同一份 ts-grm 类型样例(本项目全部实体类:书库 / 会员 / 任务 / 订单域共 12 个实体,附 6 组真实查询——覆盖继承(两种)、多态取形、embedded 复合主键、m2o/m2m、公式与软删除,代码量中等偏少)分别交给 TS7 原生编译器与 TS 5.9(JS)跑 tsc --noEmit 的真实耗时。点击按钮即可复测——数据不是截图,是刚跑的。

BENCHMARK / TSC --NOEMITts-grm 全部实体类样例 · 12 实体
import { model, dto, prop } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'

// 书库域:BookStore(abstract) ← Physical/Online(独立子表 + 判别列)
const BookStore = model.abstract("BookStore", "id", class {
    id = prop.i64()
    name = prop.str(100)
}, ctx => ctx.table({ discriminator: "TYPE" }))

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()
    authors = prop.m2m(() => Author).joinTable({ name: "book_author_mapping" })
})

const ShopView = dto.view(Book, c => [
    c.$allScalars,
    c.store.with(c => [c.id, c.name]),
    c.authors.with(c => [c.id, c.name]),
])

const rows = await sqlClient.createQuery(Book, (q, book) => {
    q.where(book.price.between(40, 80))
    q.orderBy(book.name)
    return q.select(book.fetch(ShopView))
}).fetchList()

// 共 12 个实体:书库/会员/任务/订单域 + 6 组查询
// 覆盖:继承(两种)、多态取形、embedded 复合键、m2o/m2m、公式、软删除

同一份类型代码,分别交给 TypeScript 7 原生编译器TS 5.9(JS)tsc --noEmit,只测谁先检查完。