tyno-orm
v1.0.3
Published
orm mysql
Readme
tyno-orm
轻量级 TypeScript ORM,支持 MySQL,设计参考 ThinkPHP 的链式操作风格。
快速开始
npm install tyno-orm mysql2配置数据库
import { Db } from 'tyno-orm';
Db.setConfig({
default: {
host: 'localhost',
port: 3306,
user: 'root',
password: '',
database: 'my_db',
prefix: 'ty_', // 可选:表前缀
}
});定义模型
import { Model } from 'tyno-orm';
class User extends Model {
static table = 'user'; // 表名(不设置则自动从类名驼峰转下划线)
static pk = 'id'; // 主键,默认 'id'
static autoWriteTimestamp = true; // 自动写入时间戳
static createTime = 'create_time';
static updateTime = 'update_time';
}核心功能
1. 模型自动表名
子类不设置 table 时,自动将类名从驼峰转为下划线作为表名:
| 类名 | 自动表名 |
|------|---------|
| User | user |
| UserProfile | user_profile |
| OrderItem | order_item |
| BlogPostContent | blog_post_content |
class UserProfile extends Model {}
// table 自动为 'user_profile',无需手动指定2. 查询构造器(链式调用)
所有条件方法支持链式调用,返回 Query 实例,终止方法返回实际数据。
WHERE 条件
// 基础条件 (field, value) — 省略操作符默认 =
User.where('status', 1)
// 三参数 (field, operator, value)
User.where('age', '>', 18)
// 对象形式 — 自动展开为 AND 条件
User.where({ status: 1, age: 20 })
// 闭包子查询 — 生成 ( ... ) 嵌套
User.where(q => q.where('status', 1).whereOr('age', '>', 18))
// SQL: SELECT * FROM user WHERE (status = 1 OR age > 18)// OR 条件
User.where('status', 1).whereOr('name', '张三')
// IN / NOT IN
User.whereIn('id', [1, 2, 3])
User.whereNotIn('id', [4, 5, 6])
// BETWEEN / NOT BETWEEN
User.whereBetween('age', 18, 30)
User.whereNotBetween('age', 18, 30)
// NULL / NOT NULL
User.whereNull('deleted_at')
User.whereNotNull('update_time')
// 原始 SQL 表达式
User.whereRaw('age > 18 AND status IN (1, 2)')字段选择
User.field('id', 'name', 'email') // 多参数分别传
User.field('id, name') // 单字符串也支持排序
User.orderBy('create_time', 'desc') // 默认 asc,可指定 desc
User.orderBy('age').orderBy('create_time', 'desc') // 多字段排序
User.orderRaw('FIELD(status, 1, 2, 3)') // 原始排序表达式分页与数量限制
User.limit(10) // LIMIT
User.limit(10).offset(20) // LIMIT + OFFSET
User.page(2, 15) // 快捷分页:page(页码, 每页条数)
// 分页查询:一次执行 count + select,返回总数和当前页数据
const { total, list } = await User.paginate(1, 10)
// 带条件的分页查询
const { total, list } = await User
.where('status', 1)
.orderBy('id', 'desc')
.paginate(1, 10)GROUP BY / HAVING
User.field('status, COUNT(*) as count')
.groupBy('status')
.having('count', '>', 10)
User.field('status, COUNT(*) as count')
.groupBy('status')
.havingRaw('COUNT(*) > 10')JOIN
User.join('orders', 'user.id = orders.user_id') // INNER JOIN
User.leftJoin('orders', 'user.id = orders.user_id') // LEFT JOIN
User.rightJoin('orders', 'user.id = orders.user_id') // RIGHT JOIN
User.join('orders o', 'user.id = o.user_id') // 带别名行锁
User.where('id', 1).lock('for update') // SELECT ... FOR UPDATE
User.where('id', 1).lock('share') // SELECT ... FOR SHAREUNION
const q1 = User.field('id, name');
const q2 = User.field('id, name').where('status', 1);
q1.union(q2) // UNION
q1.union(q2, 'union all') // UNION ALLSQL 调试
// fetchSql 模式:不执行 SQL,只输出
User.where('status', 1).fetchSql().select()
// 控制台打印: SELECT * FROM `ty_user` WHERE `status` = 1
// 获取生成的 SQL 和绑定参数
const q = User.where('status', 1).where('age', '>', 18)
console.log(q.toSql()) // 已代入参数的可读 SQL
console.log(q.getBindings()) // 绑定参数数组3. 终止操作
所有终止方法返回 Promise,执行实际数据库操作。
// 查询多条记录 → 返回 Model 实例数组
const users = await User.where('status', 1).limit(10).select()
// 查询单条记录 → 返回 Model 实例或 null
const user = await User.where('name', '张三').find()
const user = await User.find(1) // 按主键查询
// 聚合
const total = await User.count() // COUNT(*)
const count = await User.where('status', 1).count() // 条件计数
const maxAge = await User.max('age') // MAX
const minAge = await User.min('age') // MIN
const sum = await User.sum('score') // SUM
const avg = await User.avg('score') // AVG
// 分页查询 → 一次返回 total + list
const { total: pageTotal, list } = await User.field('id, name').orderBy('id', 'asc').paginate(1, 10)4. 写入操作
// 插入 → 返回影响行数
await User.insert({ name: '李四', age: 28, status: 1 })
// 插入并返回自增 ID
const id = await User.insertGetId({ name: '李四', age: 28, status: 1 })
// 批量插入
await User.insertAll([
{ name: '张三', age: 20 },
{ name: '李四', age: 25 },
])
// 更新 → 需提前设置 WHERE 条件
await User.where('id', 1).update({ name: '张三丰' })
// 或使用模型静态方法直接更新
await User.update({ name: '张三丰' }, { id: 1 })
// 删除
await User.where('id', 1).delete()
await User.destroy(1) // 按主键删除
// 创建实例(工厂方法,返回 Proxy 包装的 Model 实例)
const newUser = User.create({ name: '王五', age: 22, status: 1 })
await newUser.save() // 直接 INSERT
// new + .属性名 = 赋值 + .save() 模式
const user = new User()
user.name = '测试'
user.age = 25
user.status = 1
await user.save() // INSERT,自动回填主键
user.name = '测试更新'
await user.save() // UPDATE,_exists 已为 true5. 模型实例操作
从 select() 或 find() 返回的是 Model 实例,提供丰富的实例方法和语法糖。
5.1 属性访问:.name 语法
Model 实例通过 Proxy 代理,支持像访问普通对象属性一样直接读写字段,同时自动触发获取器/设置器:
const user = await User.find(1)
// 读取:直接 .属性名 访问,自动走 getAttribute
user.name // 等价于 user.getAttribute('name')
user.id // 1
user.status // 获取器 getStatusAttr 会被触发
// 写入:直接赋值,自动走 setAttribute
user.name = '张三' // 等价于 user.setAttribute('name', '张三')
user.age = 28 // 设置器 setAgeAttr 会被触发Proxy 访问优先级:
- 如果属性存在于
_attributes字典中 → 走getAttribute()(触发获取器) - 如果属性是已预加载的关联名 → 返回关联数据
- 否则 → 走原生
Reflect.get(访问实例自身的属性和方法)
// _attributes 中有 'name' → getAttribute('name')
user.name
// 已预加载的关联 → 返回关联模型数据
user.profile // User.with(['profile']) 预加载后可直接 .profile 访问
// 调用实例方法 → 正常 Reflect.get
user.save()
user.toJSON()5.2 get / setAttribute 方法
除了 .name 语法糖,也可以显式调用方法:
// 设置单个属性(会触发 setXxxAttr 设置器)
user.setAttribute('name', '新名字')
user.setAttribute('age', 28)
// 批量填充(会逐字段触发设置器)
user.fill({ name: '张三', age: 28, status: 1 })
// 获取单个属性(会触发 getXxxAttr 获取器)
user.get('name')
user.getAttribute('name')5.3 save() — 自动判断 INSERT 还是 UPDATE
save() 是 Model 最核心的实例方法。它根据实例的 _exists 标记自动判断执行插入还是更新:
// 场景 1:新建实例 → INSERT
const user = new User({ name: '张三', age: 20, status: 1 })
// _exists = false(没有主键值,且 constructor data 中无 pk)
await user.save() // 执行 INSERT,自动回填主键到实例
// 场景 2:从数据库查出的实例 → UPDATE
const user = await User.find(1)
// _exists = true(options.exists = true)
user.name = '张三丰'
await user.save() // 执行 UPDATE WHERE id = 1save() 内部流程:
save()
├── _exists === false(新建)
│ ├── convertToDb() 收集 _attributes 所有数据
│ ├── processTimestamps() 若开启 autoWriteTimestamp,自动写入时间戳
│ └── insertGetId() 执行 INSERT,拿到自增 ID
│ └── 回填 this._attributes[pk] = 新ID
│
├── _exists === true(已有记录)
│ ├── convertToDb() 收集 _attributes 所有数据
│ └── update() 执行 UPDATE WHERE pk = ?
│
└── syncOriginal() 将当前数据同步为 _original,isDirty() 重置_exists 判断规则(构造函数中):
// 从数据库查询 → exists = true
const user = await User.find(1)
// constructor(data, { exists: true }) → _exists = true
// 手动 new,data 中有主键值 → 视情况
const user = new User({ id: 1, name: '张三' })
// data['id'] 存在 → 隐式标记 _exists = true(认为从数据库来的)
// 此时 save() = UPDATE
const user = new User({ name: '张三' })
// data 中无主键 → _exists = false
// 此时 save() = INSERT
// 通过 options 显式控制
const user = new User({ id: 1, name: '张三' }, { exists: false })
// 强制 _exists = false,save() = INSERT5.4 delete() — 删除实例
const user = await User.find(1)
await user.delete()
// 执行 DELETE FROM user WHERE id = 1
// _exists 标记置为 false
// 不存在的实例调用 delete() 会抛错
const u = new User({ name: 'test' })
await u.delete() // Error: Model does not exist5.5 数据变更检测
const user = await User.find(1)
user.getOriginal('name') // '张三' — 获取从数据库查出时的原始值
user.getOriginal() // { id: 1, name: '张三', ... } — 全部原始值
user.name = '张三丰'
user.isDirty() // true — 当前数据和原始数据不一致
await user.save() // 保存后 syncOriginal() 被调用
user.isDirty() // false — 重新同步5.6 序列化
const user = await User.find(1)
console.log(user.toJSON())
// → { id: 1, name: '张三', ... }
console.log(JSON.stringify(user))
// toJSON() 在 JSON.stringify 中自动调用6. 获取器 / 设置器
定义 getXxxAttr / setXxxAttr 方法,自动在读写属性时调用:
class User extends Model {
// 读取 full_name 时自动拼接
getFullNameAttr(value: string, data: Record<string, any>) {
return `${data.first_name} ${data.last_name}`
}
// 写入 password 时自动哈希
setPasswordAttr(value: string) {
return hashSync(value, 10)
}
}
const user = await User.find(1)
console.log(user.full_name) // 自动触发 getFullNameAttr7. 序列化控制
class User extends Model {
static hidden = ['password', 'secret'] // 隐藏字段
static visible = ['id', 'name', 'email'] // 仅可见字段
static append = ['full_name', 'role'] // 追加计算属性
}
user.toJSON()
// hidden 模式下排除 password, secret
// visible 模式下只包含 id, name, email
// append 追加 full_name, role8. 自动时间戳
class User extends Model {
static autoWriteTimestamp = true
static createTime = 'create_time'
static updateTime = 'update_time'
}
// insert 时自动写入 create_time 和 update_time
// update 时自动更新 update_time
await User.insert({ name: '张三' })
await User.where('id', 1).update({ name: '张三丰' })9. 关联预加载
四种关联类型通过 Model 基类的实例方法定义,无需外部 import 关联类。
9.1 关联类型概览
| 关联类型 | 关系 | 外键位置 | 查询结果 |
|---------|------|---------|---------|
| hasOne() | 一对一 | 关联表 | 单条 Model 或 null |
| hasMany() | 一对多 | 关联表 | Model 数组 |
| belongsTo() | 多对一 | 当前表 | 单条 Model 或 null |
| belongsToMany() | 多对多 | 中间表 | Model 数组 |
9.2 hasOne() — 一对一
假设 User 有一个 Profile:
user 表:id, name, ...
profile 表:id, user_id, avatar, bio, ... ← user_id 是外键class Profile extends Model {
static table = 'profile'
static pk = 'id'
}
class User extends Model {
static table = 'user'
profile() {
return this.hasOne(Profile, 'user_id', 'id')
// 关联模型 外键(关联表) 主键(当前表)
}
}// 预加载后可直接 .profile 访问
const user = await User.with(['profile']).find(1)
console.log(user.profile?.avatar)
// 或直接获取关联数据
const profile = await new User({ id: 1 }).profile().getResults()
// → SELECT * FROM `profile` WHERE `user_id` = 1 LIMIT 19.3 hasMany() — 一对多
假设 User 有多条 Order:
user 表:id, name, ...
order 表:id, user_id, order_no, total, ... ← user_id 是外键class Order extends Model {
static table = 'order'
}
class User extends Model {
static table = 'user'
orders() {
return this.hasMany(Order, 'user_id', 'id')
// 关联模型 外键(关联表) 主键(当前表)
}
}const user = await User.with(['orders']).find(1)
console.log(user.orders) // Order 实例数组
console.log(user.orders[0].order_no) // 第一笔订单号9.4 belongsTo() — 多对一
与 hasOne/hasMany 相反,外键在当前表上。假设 Order 属于一个 User:
order 表:id, user_id, order_no, ... ← user_id 是外键(在当前表)
user 表:id, name, ...class Order extends Model {
static table = 'order'
user() {
return this.belongsTo(User, 'user_id', 'id')
// 关联模型 外键(当前表) 主键(关联表)
}
}const order = await Order.with(['user']).find(1)
console.log(order.user) // 关联的 User 实例
console.log(order.user.name)9.5 belongsToMany() — 多对多
通过中间表关联。假设 User 和 Role 多对多:
user 表:id, name, ...
role 表:id, name, ...
user_role 表:user_id, role_id ← 中间表class Role extends Model {
static table = 'role'
}
class User extends Model {
static table = 'user'
roles() {
return this.belongsToMany(
Role, // 关联模型类
'user_role', // 中间表名
'role_id', // 中间表指向关联模型的键
'user_id', // 中间表指向当前模型的键
'id', // 当前模型主键
'id' // 关联模型主键
)
}
}// BelongsToMany 生成的 SQL:
// SELECT `role`.*
// FROM `role`
// INNER JOIN `user_role` ON `user_role`.`role_id` = `role`.`id`
// WHERE `user_role`.`user_id` = ?
const user = await User.with(['roles']).find(1)
console.log(user.roles) // Role 实例数组9.6 预加载 with() — 避免 N+1 查询
with() 方法使用 eagerLoad 批量预加载,一次查询加载所有关联数据,避免循环查询:
// ❌ N+1 查询:每条 User 各查一次 Profile
const users = await User.select()
for (const u of users) {
const profile = await u.profile().getResults() // 每条触发一次 SQL
}
// ✅ 预加载:2 条 SQL 搞定(1 条用户 + 1 条关联)
const users = await User.with(['profile', 'orders']).select()
// 内部调用 HasOne.eagerLoad() 和 HasMany.eagerLoad()
// 将关联数据注入到每个 user 实例的 _relations 中
// 之后直接 .profile / .orders 访问即可预加载内部流程(以 User.with(['profile']).select() 为例):
User.with(['profile']).select()
├── 1. query() 取出用户列表 → users: Model[]
├── 2. HasOne.eagerLoad(users, Profile, 'user_id', 'id')
│ ├── 收集所有 users 的 id → [1, 2, 3, ...]
│ ├── 执行:SELECT * FROM profile WHERE user_id IN (1, 2, 3, ...)
│ └── 按 user_id 分组注入到各 user._relations['profile']
└── 3. 用户可通过 user.profile 访问(Proxy 读取 _relations)9.7 延迟加载
不通过 with(),直接调用关联方法获取数据(每次调用都执行 SQL):
const user = new User({ id: 1 })
// hasOne → 返回单条
const profile = await user.profile().getResults()
// hasMany → 返回数组
const orders = await user.orders().getResults()
// belongsTo → 返回单条
const order = new Order({ id: 1, user_id: 5 })
const owner = await order.user().getResults()9.8 Model 关联方法签名
class Model {
// 一对一:关联表有外键指向当前表
hasOne(related: typeof Model, foreignKey: string, localKey: string): HasOne
// 一对多:关联表有外键指向当前表
hasMany(related: typeof Model, foreignKey: string, localKey: string): HasMany
// 多对一:当前表有外键指向关联表
belongsTo(related: typeof Model, foreignKey: string, ownerKey: string): BelongsTo
// 多对多:通过中间表关联
belongsToMany(
related: typeof Model,
pivotTable: string, // 中间表名
relatedPivotKey: string, // 中间表→关联模型的键
parentPivotKey: string, // 中间表→当前模型的键
parentKey: string, // 当前模型主键
relatedKey: string // 关联模型主键
): BelongsToMany
}10. 模型事件
通过 EventManager 为模型挂载生命周期钩子,在 CRUD 操作的前后插入自定义逻辑。
10.1 事件类型
| 事件 | 触发时机 |
|------|---------|
| beforeInsert | 插入前 |
| afterInsert | 插入后 |
| beforeUpdate | 更新前 |
| afterUpdate | 更新后 |
| beforeWrite | insert 或 update 前都会触发 |
| afterWrite | insert 或 update 后都会触发 |
| beforeDelete | 删除前 |
| afterDelete | 删除后 |
| beforeFind | 查询前 |
| afterFind | 查询后 |
10.2 注册监听
每个模型类持有一个独立的 EventManager:
import { EventManager } from 'tyno-orm'
class User extends Model {
static table = 'user'
static events = new EventManager()
}
// 注册事件监听
User.events.on('beforeInsert', (data: Record<string, any>) => {
console.log('即将插入:', data)
})
User.events.on('afterInsert', (id: number | string) => {
console.log('插入成功, id =', id)
})
User.events.on('beforeUpdate', (data: Record<string, any>) => {
data.updated_at = new Date().toISOString() // 修改即将写入的数据
})
User.events.on('afterDelete', (id: any) => {
console.log('已删除记录:', id)
})
User.events.on('beforeFind', () => {
console.log('执行查询前')
})
User.events.on('afterFind', (results: any[]) => {
console.log('查询完成,结果数:', results.length)
})10.3 beforeWrite / afterWrite
通用型事件,不区分 insert 和 update,只要发生写入就触发:
// insert 时会触发:beforeWrite → beforeInsert → [SQL] → afterInsert → afterWrite
// update 时会触发:beforeWrite → beforeUpdate → [SQL] → afterUpdate → afterWrite
User.events.on('beforeWrite', (data: Record<string, any>) => {
// insert 和 update 都会经过这里
data.operator = 'system'
})
User.events.on('afterWrite', () => {
console.log('写入完成')
})10.4 移除监听
const handler = (data: Record<string, any>) => {
console.log(data)
}
User.events.on('beforeInsert', handler)
// 不再需要时移除
User.events.off('beforeInsert', handler)
// 清空所有事件
User.events.clear()10.5 事件触发流程
User.insert({ name: '张三' })
├── beforeWrite(data) ← 通用写入前,可修改 data
├── beforeInsert(data) ← 插入前,可修改 data
├── 执行 INSERT 语句
├── afterInsert(id) ← 插入后,参数为自增 ID
└── afterWrite() ← 通用写入后
User.where('id', 1).update({ name: '张三丰' })
├── beforeWrite(data) ← data = { name: '张三丰' }
├── beforeUpdate(data)
├── 执行 UPDATE 语句
├── afterUpdate()
└── afterWrite()
User.destroy(1)
├── beforeDelete()
├── 执行 DELETE 语句
└── afterDelete(id)
User.select()
├── beforeFind()
├── 执行 SELECT 语句
└── afterFind(results) ← 参数为查询结果数组10.6 完整示例:日志审计
class Log extends Model {
static table = 'audit_log'
}
class User extends Model {
static table = 'user'
static events = new EventManager()
static boot() {
this.events.on('beforeInsert', (data: Record<string, any>) => {
data.password = hashSync(data.password, 10)
})
this.events.on('afterInsert', async (id: number | string) => {
await Log.insert({
action: 'user.create',
target_id: id,
created_at: new Date().toISOString()
})
})
this.events.on('afterUpdate', async () => {
await Log.insert({
action: 'user.update',
target_id: 0, // ...从上下文获取
created_at: new Date().toISOString()
})
})
this.events.on('afterDelete', async (id: any) => {
await Log.insert({
action: 'user.delete',
target_id: id,
created_at: new Date().toISOString()
})
})
}
}
// 应用启动时调用
User.boot()10.7 EventManager API
class EventManager {
// 注册事件监听(支持同一事件多个回调)
on(event: ModelEvent, callback: Function): void
// 移除指定事件的某个回调
off(event: ModelEvent, callback: Function): void
// 触发事件(内部容错:单个回调异常不影响其他回调)
trigger(event: ModelEvent, ...args: any[]): void
// 清空所有事件的监听
clear(): void
}11. 数据库事务
Db.transaction() 封装了 MySQL 的事务流程:获取连接 → 开启事务 → 执行业务 → 提交/回滚 → 释放连接。
11.1 基本用法
await Db.transaction(async (conn) => {
await User.insert({ name: '张三' })
await User.insert({ name: '李四' })
// 任何异常自动回滚
})11.2 事务原理
Db.transaction() 内部流程:
Db.transaction(callback)
├── 1. Db.connect(name) 获取连接池
├── 2. conn.getConnection() 从池中取出一个物理连接
├── 3. poolConn.beginTransaction() 开启事务
├── 4. callback(poolConn) 执行业务逻辑(传入原始连接)
│ └── 用户在此执行 insert/update/delete 等操作
├── 5. poolConn.commit() 提交事务
├── 6. 返回 callback 的返回值
└── 异常时:
├── poolConn.rollback() 回滚事务
└── throw e 重新抛出异常
└── finally:
└── poolConn.release() 归还连接到池11.3 手动事务(不推荐,除非有特殊需求)
通过 Connection 直接获取物理连接,完全手动控制:
import { Db } from 'tyno-orm'
const conn = Db.connect()
const poolConn = await conn.getConnection()
try {
await poolConn.beginTransaction()
// 方式 1:通过 Query 实例执行
const q = Db.table('user')
const { sql, bindings } = q.query().field('id, name').buildSelect()
await poolConn.execute(sql, bindings)
// 方式 2:直接在连接上执行原始 SQL
await poolConn.execute('UPDATE user SET status = ? WHERE id = ?', [1, 5])
await poolConn.commit()
} catch (e) {
await poolConn.rollback()
throw e
} finally {
poolConn.release() // 必须手动释放!
}11.4 事务中的返回值
transaction() 将 callback 的返回值透传回来:
const newUserId = await Db.transaction(async (conn) => {
const id = await User.insertGetId({ name: '张三', status: 1 })
await User.where('id', id).update({ status: 2 }) // 用 INSERT 拿到的 ID 做后续操作
return id
})
console.log('新用户 id:', newUserId) // 拿到事务中返回的值11.5 指定连接名称
多数据库场景下指定使用哪个连接的事务:
Db.setConfig({
default: { /* ... */ },
bbs: { /* ... */ },
})
// 在 bbs 数据库上开启事务
await Db.transaction(async (conn) => {
// 注意:事务中需要用对应连接的 Query,直接调用 SQL
}, 'bbs')11.6 事务注意事项
- 连接隔离:
transaction()内部用getConnection()获取独立连接,不会与其他请求共享,提交/回滚后才归还连接池 - 自动回滚:callback 中任何 throw 的异常都会触发
rollback() - 必须释放:无论成功还是失败,
finally块确保poolConn.release()被调用 - 事务中的 errno:如果事务中某条 SQL 失败(如唯一键冲突),MySQL 的连接状态会出错,后续操作需要手动处理。推荐在 callback 中捕获异常并主动 throw
12. 原始查询
import { Db } from 'tyno-orm'
// 通过 Db.table 快速查询
const rows = await Db.table('user').where('status', 1).select()
// 原始 SQL 表达式
import { Raw } from 'tyno-orm'
User.field(new Raw('COUNT(*) as count, SUM(score) as total'))
User.where(new Raw('age > 18'))13. 数据库连接
// 获取连接
const conn = Db.connect() // 默认连接
const conn2 = Db.connect('bbs') // 指定连接名
// 模型指定连接
class User extends Model {
static connectionName = 'bbs'
}
// 多数据库配置
Db.setConfig({
default: { /* ... */ },
bbs: { /* ... */ },
})14. 复杂查询示例
// 多表关联 + 条件 + 排序 + 分页
const users = await User
.field('u.id, u.name, o.order_no, o.total')
.alias('u')
.join('orders o', 'u.id = o.user_id')
.leftJoin('profile p', 'u.id = p.user_id')
.where('u.status', '=', 1)
.orderBy('u.create_time', 'desc')
.page(1, 20)
.select()
// 聚合查询
const stats = await User
.field('status, COUNT(*) as count, AVG(age) as avg_age')
.groupBy('status')
.having('count', '>', 5)
.orderBy('count', 'desc')
.select()完整 CRUD 流程示例
import { Db, Model } from 'tyno-orm'
Db.setConfig({ default: { host: 'localhost', port: 3306, user: 'root', password: '', database: 'my_db' } })
class User extends Model {
static table = 'user'
static autoWriteTimestamp = true
}
// 插入
const id = await User.insertGetId({ name: '张三', email: '[email protected]', status: 1 })
// 查询
const user = await User.find(id)
console.log(user.toJSON())
// 更新
user.setAttribute('name', '张三丰')
await user.save()
// 删除
await user.delete()项目结构
src/
├── builder/
│ └── mysql-builder.ts # MySQL SQL 方言编译
├── model/
│ ├── relations/ # 关联:HasOne, HasMany, BelongsTo, BelongsToMany
│ │ ├── relation.ts
│ │ ├── has-one.ts
│ │ ├── has-many.ts
│ │ ├── belongs-to.ts
│ │ └── belongs-to-many.ts
│ ├── events.ts # 模型事件系统
│ └── model.ts # 模型基类
├── query/
│ ├── state.ts # 查询状态工厂
│ └── query.ts # 查询构造器
├── connection.ts # 数据库连接(连接池)
├── db.ts # 门面类
├── raw.ts # 原始 SQL 表达式
├── types.ts # 类型定义
└── index.ts # 导出入口