Universal database abstraction layer for Zhin.js - supports SQLite, MySQL, PostgreSQL, MongoDB, Redis
npm install @zhin.js/databaseUniversal database abstraction layer for Zhin.js framework with support for multiple database backends.
``bash`
npm install @zhin.js/database
Install the database driver you need:
`bashFor SQLite
npm install sqlite3
Quick Start
`typescript
import { Registry } from '@zhin.js/database';// 创建数据库实例(以 SQLite 为例)
const db = Registry.create('sqlite', {
filename: './database.sqlite'
}, {
users: {
id: { type: 'integer', primary: true, autoIncrement: true },
name: { type: 'string', nullable: false },
email: { type: 'string', unique: true }
}
});
await db.start();
// 使用模型进行 CRUD 操作
const userModel = db.model('users');
const user = await userModel.create({
name: 'John Doe',
email: 'john@example.com'
});
`Supported Databases
$3
- SQLite - 内置支持,需要安装 sqlite3
- 轻量级、零配置
- 适合中小型应用
- 支持 WAL 模式
- MySQL - 内置支持,需要安装 mysql2
- 完整的关系型数据库特性
- 高性能、可扩展
- 广泛使用- PostgreSQL - 内置支持,需要安装
pg
- 强大的企业级数据库
- 支持高级 SQL 特性
- JSON 支持$3
- MongoDB - 内置支持,需要安装 mongodb
- 文档型数据库
- 灵活的 Schema
- 适合非结构化数据- Redis - 内置支持,需要安装
redis
- 键值存储
- 高性能缓存
- 支持多种数据结构$3
- Memory - 内置支持,无需额外安装
- 完全在内存中运行
- 适合测试和临时数据
- 零配置Usage Examples
$3
`typescript
import { Registry } from '@zhin.js/database';const db = Registry.create('sqlite', {
filename: './data/bot.db',
mode: 'wal' // Write-Ahead Logging 模式
}, {
users: {
id: { type: 'integer', primary: true, autoIncrement: true },
name: { type: 'string', nullable: false },
createdAt: { type: 'timestamp', default: 'CURRENT_TIMESTAMP' }
}
});
await db.start();
`$3
`typescript
const db = Registry.create('mysql', {
host: 'localhost',
port: 3306,
user: 'root',
password: 'password',
database: 'myapp'
}, schemas);await db.start();
`$3
`typescript
const db = Registry.create('pg', {
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'password',
database: 'myapp'
}, schemas);await db.start();
`$3
`typescript
const db = Registry.create('mongodb', {
url: 'mongodb://localhost:27017',
dbName: 'myapp'
}, schemas);await db.start();
`$3
`typescript
const db = Registry.create('redis', {
socket: {
host: 'localhost',
port: 6379
}
}, schemas);await db.start();
`$3
`typescript
const db = Registry.create('memory', {}, schemas);
await db.start();
`Database Types
$3
适用于关系型数据库 (SQLite, MySQL, PostgreSQL)
- 支持 SQL 查询
- 支持事务
- 支持索引和约束$3
适用于文档型数据库 (MongoDB)
- 灵活的 Schema
- 支持嵌套文档
- 支持丰富的查询操作$3
适用于键值存储 (Redis)
- 高性能读写
- 支持多种数据结构
- 支持过期时间Features
$3
- 🎯 类型安全: 完整的 TypeScript 类型支持
- 🔄 统一 API: 所有数据库类型使用相同的接口
- 🔍 查询构建器: 流畅的链式查询 API
- 📋 Schema 管理: 自动创建表/集合
- 🔌 连接管理: 自动处理连接和重连
- 💾 事务支持: 内置事务支持(关系型数据库)
- 🔄 迁移支持: Schema 演进和版本管理$3
- 无需额外配置即可使用
- 自动检测并安装相应的数据库驱动
- 完整的错误处理和日志记录$3
- 连接池管理
- 查询优化
- 批量操作支持Model API
`typescript
const model = db.model('users');// Create
const user = await model.create({ name: 'John', email: 'john@example.com' });
// Read
const users = await model.find({ name: 'John' });
const user = await model.findOne({ email: 'john@example.com' });
// Update
await model.update({ name: 'John' }, { name: 'Jane' });
// Delete
await model.remove({ name: 'Jane' });
// Count
const count = await model.count({ email: { $like: '%@example.com' } });
// Pagination
const result = await model.find({}, { limit: 10, offset: 0 });
`链式查询 API (Fluent Query Builder)
链式查询提供了一种流畅、类型安全的方式来构建数据库查询。所有查询都是 Thenable 的,可以直接使用
await 或 .then() 执行。$3
`typescript
// 从数据库实例使用 - select(表名, 字段数组)
const users = await db
.select('users', ['id', 'name', 'email'])
.where({ status: 'active' })
.orderBy('createdAt', 'DESC')
.limit(10);// 从模型实例使用 - select(...字段) 展开参数
const model = db.model('users');
const users = await model
.select('id', 'name', 'email') // 展开参数,不是数组
.where({ status: 'active' })
.orderBy('createdAt', 'DESC')
.limit(10);
// 选择所有字段
const allUsers = await model.select();
`$3
`typescript
// 从数据库实例:select(表名, 字段数组)
const users = await db
.select('users', ['id', 'name', 'email'])
.where({ age: { $gte: 18 } })
.groupBy('department')
.orderBy('name', 'ASC')
.limit(20)
.offset(0);// 从模型实例:select(...字段)
const model = db.model('users');
const users = await model
.select('id', 'name', 'email') // 展开参数
.where({ age: { $gte: 18 } })
.groupBy('department')
.orderBy('name', 'ASC')
.limit(20)
.offset(0);
// 链式方法说明
// .where(condition) - 添加查询条件
// .groupBy(...fields) - 分组字段
// .orderBy(field, dir) - 排序(ASC/DESC)
// .limit(count) - 限制返回数量
// .offset(count) - 跳过指定数量
`$3
`typescript
// 从数据库实例
const newUser = await db.insert('users', {
name: 'Alice',
email: 'alice@example.com',
age: 25
});// 从模型实例
const model = db.model('users');
const newUser = await model.insert({
name: 'Alice',
email: 'alice@example.com',
age: 25
});
`$3
`typescript
// 从数据库实例:update(表名, 更新数据).where(条件)
const affectedRows = await db
.update('users', { status: 'inactive' })
.where({ lastLogin: { $lt: new Date('2024-01-01') } });// 从模型实例:update(更新数据).where(条件)
const model = db.model('users');
const count = await model
.update({ verified: true })
.where({
email: { $like: '%@company.com' },
status: 'pending'
});
`$3
`typescript
// 从数据库实例:delete(表名, 初始条件).where(额外条件)
const deletedUsers = await db
.delete('users', { status: 'deleted' })
.where({ deletedAt: { $lt: new Date('2023-01-01') } });// 从模型实例:delete(条件)
const model = db.model('users');
const deleted = await model.delete({ status: 'banned' });
`$3
`typescript
// 从数据库实例
await db.alter('users', {
avatar: { action: 'add', type: 'string', nullable: true },
oldField: { action: 'drop' },
name: { action: 'modify', type: 'string', nullable: false }
});// 从模型实例
const model = db.model('users');
await model.alter({
newColumn: { action: 'add', type: 'integer', default: 0 }
});
`$3
关系型模型提供了更便捷的高级方法:
`typescript
const model = db.model('users'); // RelatedModel// create - 创建单条数据
const user = await model.create({ name: 'John', email: 'john@example.com' });
// createMany - 批量创建
const users = await model.createMany([
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' }
]);
// selectOne - 查找单条数据
const user = await model.selectOne({ email: 'john@example.com' });
// selectById - 根据 ID 查找
const user = await model.selectById(1);
// updateOne - 更新单条数据
const success = await model.updateOne({ id: 1 }, { name: 'Jane' });
// updateById - 根据 ID 更新
const success = await model.updateById(1, { status: 'active' });
// deleteById - 根据 ID 删除
const success = await model.deleteById(1);
// count - 统计数量
const total = await model.count({ status: 'active' });
`$3
文档型模型(MongoDB)的特有方法:
`typescript
const model = db.model('users'); // DocumentModel// create - 创建文档(自动生成 _id)
const user = await model.create({ name: 'John', email: 'john@example.com' });
// 返回: { name: 'John', email: '...', _id: 'abc123...' }
// 批量创建
const users = await model.create([
{ name: 'Alice' },
{ name: 'Bob' }
]);
// selectOne - 查找单个文档
const user = await model.selectOne('name', 'email');
// selectById - 根据 _id 查找
const user = await model.selectById('abc123...');
// updateById - 根据 _id 更新
await model.updateById('abc123...', { name: 'Jane' });
// deleteById - 根据 _id 删除
await model.deleteById('abc123...');
`$3
键值存储(Redis)的特有方法:
`typescript
const model = db.model('cache'); // KeyValueModel// set/get - 基本键值操作
await model.set('user:1', { name: 'John', age: 25 });
const user = await model.get('user:1');
// 带过期时间(秒)
await model.set('session:abc', { userId: 1 }, 3600); // 1小时后过期
// has - 检查键是否存在
const exists = await model.has('user:1');
// deleteByKey - 删除键
await model.deleteByKey('user:1');
// keys/values/entries - 遍历
const allKeys = await model.keys();
const allValues = await model.values();
const allEntries = await model.entries();
// 模式匹配查找
const userKeys = await model.keysByPattern('user:*');
// TTL 操作
await model.expire('session:abc', 1800); // 设置过期时间
const ttl = await model.ttl('session:abc'); // 获取剩余时间
await model.persist('session:abc'); // 移除过期时间
// 原子操作
await model.setIfNotExists('lock:resource', 'locked', 30);
await model.setIfExists('counter', newValue);
const oldValue = await model.getAndSet('key', newValue);
const value = await model.deleteAndGet('key');
// 批量操作
await model.setMany([['key1', 'value1'], ['key2', 'value2']], 3600);
// 清理
await model.clear(); // 清空所有键
await model.cleanup(); // 清理过期键
const size = await model.size(); // 获取键数量
`$3
#### 比较操作符
| 操作符 | 说明 | 示例 |
|--------|------|------|
|
$eq | 等于 | { age: { $eq: 18 } } |
| $ne | 不等于 | { status: { $ne: 'deleted' } } |
| $gt | 大于 | { age: { $gt: 18 } } |
| $gte | 大于等于 | { age: { $gte: 18 } } |
| $lt | 小于 | { age: { $lt: 65 } } |
| $lte | 小于等于 | { age: { $lte: 65 } } |
| $in | 在列表中 | { role: { $in: ['admin', 'mod'] } } |
| $nin | 不在列表中 | { status: { $nin: ['banned', 'deleted'] } } |
| $like | 模糊匹配 | { email: { $like: '%@gmail.com' } } |
| $nlike | 不匹配 | { name: { $nlike: 'test%' } } |#### 逻辑操作符
`typescript
// $and - 逻辑与
const users = await model
.select('id', 'name')
.where({
$and: [
{ age: { $gte: 18 } },
{ status: 'active' }
]
});// $or - 逻辑或
const users = await model
.select('id', 'name')
.where({
$or: [
{ role: 'admin' },
{ role: 'moderator' }
]
});
// $not - 逻辑非
const users = await model
.select('id', 'name')
.where({
$not: { status: 'banned' }
});
// 组合使用
const users = await model
.select('id', 'name', 'email')
.where({
$and: [
{ age: { $gte: 18, $lte: 65 } },
{
$or: [
{ role: 'admin' },
{ verified: true }
]
}
]
});
`$3
所有查询对象都实现了
PromiseLike 接口,支持多种异步调用方式:`typescript
// 使用 await
const users = await model.select('id', 'name').where({ active: true });// 使用 .then()
model.select('id', 'name')
.where({ active: true })
.then(users => console.log(users));
// 使用 .catch() 处理错误
model.select('id', 'name')
.where({ active: true })
.catch(err => console.error(err));
// 使用 .finally()
model.select('id', 'name')
.where({ active: true })
.finally(() => console.log('Query completed'));
// 异步迭代器
for await (const user of model.select('id', 'name').where({ active: true })) {
console.log(user);
}
`$3
`typescript
import { Registry } from '@zhin.js/database';// 创建数据库
const db = Registry.create('sqlite', { filename: './app.db' }, {
users: {
id: { type: 'integer', primary: true, autoIncrement: true },
name: { type: 'string', nullable: false },
email: { type: 'string', unique: true },
age: { type: 'integer' },
role: { type: 'string', default: 'user' },
status: { type: 'string', default: 'active' },
createdAt: { type: 'timestamp', default: 'CURRENT_TIMESTAMP' }
}
});
await db.start();
// 获取模型
const userModel = db.model('users');
// 使用便捷方法创建数据
const user = await userModel.create({
name: 'John Doe',
email: 'john@example.com',
age: 28,
role: 'admin'
});
// 使用链式查询
const activeAdmins = await userModel
.select('id', 'name', 'email')
.where({
$and: [
{ role: { $in: ['admin', 'moderator'] } },
{ status: 'active' },
{ age: { $gte: 21 } }
]
})
.orderBy('name', 'ASC')
.limit(10);
// 使用便捷方法更新
await userModel.updateOne(
{ role: 'guest', createdAt: { $lt: new Date('2024-01-01') } },
{ status: 'inactive' }
);
// 或使用链式更新
await userModel
.update({ status: 'inactive' })
.where({ role: 'guest' });
// 使用便捷方法删除
await userModel.deleteById(1);
// 或使用链式删除
await userModel.delete({ status: 'banned' });
// 统计
const count = await userModel.count({ status: 'active' });
console.log(
Active users: ${count});
`高级功能
$3
支持 COUNT, SUM, AVG, MIN, MAX 等聚合函数:
`typescript
// 基本聚合
const result = await db.aggregate('orders')
.count('*', 'total_orders')
.sum('amount', 'total_amount')
.avg('amount', 'avg_amount')
.max('amount', 'max_amount')
.min('amount', 'min_amount');// 带条件和分组
const stats = await model.aggregate()
.count('*', 'count')
.sum('amount', 'total')
.where({ status: 'completed' })
.groupBy('category')
.having({ count: { $gt: 10 } });
// 结果示例
// [
// { category: 'electronics', count: 150, total: 50000 },
// { category: 'clothing', count: 80, total: 12000 }
// ]
`$3
高效的批量插入,生成单条 SQL 语句:
`typescript
// 批量插入多条记录
const result = await db.insertMany('users', [
{ name: 'Alice', email: 'alice@example.com', age: 25 },
{ name: 'Bob', email: 'bob@example.com', age: 30 },
{ name: 'Charlie', email: 'charlie@example.com', age: 35 }
]);// 从 Model 调用
await model.insertMany([
{ name: 'User1', status: 'active' },
{ name: 'User2', status: 'active' },
{ name: 'User3', status: 'pending' }
]);
// 返回: { affectedRows: 3, insertIds: [...] }
`$3
支持 SQLite, MySQL, PostgreSQL 的事务操作,支持链式调用:
`typescript
await db.transaction(async (trx) => {
// 插入
await trx.insert('orders', { userId, amount: 100 });
// 更新(支持链式 where)
await trx.update('accounts', { balance: newBalance })
.where({ userId });
// 查询(支持链式 where/orderBy/limit/offset)
const user = await trx.select('users', ['id', 'name'])
.where({ id: userId })
.orderBy('createdAt', 'DESC')
.limit(10);
// 删除(支持链式 where)
await trx.delete('temp_data')
.where({ expired: true });
// 批量插入
await trx.insertMany('logs', [
{ message: 'Order created', userId },
{ message: 'Balance updated', userId }
]);
// 也支持原生 SQL
await trx.query('UPDATE stats SET count = count + 1 WHERE type = ?', ['orders']);
// 如果所有操作成功,自动 commit
// 如果任何操作失败,自动 rollback
}, {
isolationLevel: 'REPEATABLE_READ' // 可选: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
});
`事务中支持的链式方法:
| 方法 | 说明 | 链式方法 |
|------|------|----------|
|
trx.insert(table, data) | 插入单条 | - |
| trx.insertMany(table, data[]) | 批量插入 | - |
| trx.select(table, fields) | 查询 | .where(), .orderBy(), .limit(), .offset() |
| trx.update(table, data) | 更新 | .where() |
| trx.delete(table) | 删除 | .where() |
| trx.query(sql, params) | 原生 SQL | - |$3
支持
INNER JOIN、LEFT JOIN、RIGHT JOIN,返回类型自动推断:`typescript
interface Schema {
users: { id: number; name: string; status: string };
orders: { id: number; userId: number; amount: number };
}// INNER JOIN - 返回类型自动扩展
const result = await db.select('users', ['id', 'name'])
.join('orders', 'id', 'userId')
.where({ status: 'active' });
// ✅ 类型推断正确!
// result 类型: { users: { id: number; name: string }, orders: { id: number; userId: number; amount: number } }[]
result[0].users.id; // number ✅
result[0].users.name; // string ✅
result[0].orders.amount; // number ✅
// LEFT JOIN - 右表可能为 null
const leftResult = await db.select('users', ['id', 'name'])
.leftJoin('orders', 'id', 'userId');
// leftResult 类型: { users: {...}, orders: {...} | null }[]
leftResult[0].orders?.amount; // number | undefined ✅
// RIGHT JOIN - 左表可能为 null
const rightResult = await db.select('users', ['id', 'name'])
.rightJoin('orders', 'id', 'userId');
// rightResult 类型: { users: Partial<...>, orders: {...} }[]
// 多表 JOIN - 链式调用
const multiJoin = await db.select('orders', ['id', 'amount'])
.join('users', 'userId', 'id')
.leftJoin('products', 'productId', 'id')
.where({ amount: { $gt: 100 } });
`JOIN 方法:
| 方法 | SQL | 返回类型 |
|------|-----|----------|
|
.join(table, left, right) | INNER JOIN | { 主表: {...}, 关联表: {...} } |
| .leftJoin(table, left, right) | LEFT JOIN | { 主表: {...}, 关联表: {...} \| null } |
| .rightJoin(table, left, right) | RIGHT JOIN | { 主表: Partial<...>, 关联表: {...} } |$3
启用软删除后,
delete() 不会物理删除数据,而是设置 deletedAt 字段:`typescript
import { RelatedModel } from '@zhin.js/database';// 创建带软删除的模型
const userModel = new RelatedModel(db, 'users', {
softDelete: true,
deletedAtField: 'deletedAt' // 可选,默认 'deletedAt'
});
// 删除 → 实际执行: UPDATE users SET deletedAt = NOW() WHERE id = 1
await userModel.delete({ id: 1 });
// 普通查询 → 自动排除已删除: SELECT * FROM users WHERE deletedAt IS NULL
const activeUsers = await userModel.select('id', 'name');
// 查询包含已删除的记录
const allUsers = await userModel.selectWithTrashed('id', 'name');
// 仅查询已删除的记录
const deletedUsers = await userModel.selectOnlyTrashed('id', 'name');
// 恢复已删除的记录 → UPDATE users SET deletedAt = NULL WHERE id = 1
await userModel.restore({ id: 1 });
// 强制物理删除(忽略软删除设置)
await userModel.forceDelete({ id: 1 });
`软删除方法:
| 方法 | 说明 |
|------|------|
|
model.delete(condition) | 软删除(设置 deletedAt) |
| model.select(...) | 自动排除已删除 |
| model.selectWithTrashed(...) | 包含已删除 |
| model.selectOnlyTrashed(...) | 仅已删除 |
| model.restore(condition) | 恢复软删除 |
| model.forceDelete(condition) | 物理删除 |$3
启用后自动管理
createdAt 和 updatedAt 字段:`typescript
const userModel = new RelatedModel(db, 'users', {
timestamps: true,
createdAtField: 'createdAt', // 可选
updatedAtField: 'updatedAt' // 可选
});// 插入时自动设置 createdAt 和 updatedAt
await userModel.insert({ name: 'John' });
// INSERT INTO users (name, createdAt, updatedAt) VALUES ('John', NOW(), NOW())
// 更新时自动更新 updatedAt
await userModel.update({ name: 'Jane' }).where({ id: 1 });
// UPDATE users SET name = 'Jane', updatedAt = NOW() WHERE id = 1
`$3
支持在
$in 和 $nin 操作符中使用子查询,带完整类型推断:`typescript
interface Schema {
users: { id: number; name: string; status: string };
orders: { id: number; userId: number; amount: number };
}// 查询购买过高价商品的用户
const users = await db.select('users', ['id', 'name'])
.where({
id: {
$in: db.select('orders', ['userId']).where({ amount: { $gt: 1000 } })
}
});
// SQL: SELECT id, name FROM users WHERE id IN (SELECT userId FROM orders WHERE amount > 1000)
// 查询没有下过订单的用户
const inactiveUsers = await db.select('users', ['id', 'name'])
.where({
id: {
$nin: db.select('orders', ['userId'])
}
});
// SQL: SELECT id, name FROM users WHERE id NOT IN (SELECT userId FROM orders)
// ✅ 类型安全:子查询返回类型必须与字段类型匹配
db.select('users', ['id']).where({
id: { $in: db.select('orders', ['userId']) } // ✅ number 匹配 number
});
db.select('users', ['id']).where({
id: { $in: db.select('users', ['name']) } // ❌ 类型错误!string 不能匹配 number
});
`$3
启用查询日志,方便调试和性能分析:
`typescript
// 启用默认日志(输出到控制台)
db.enableLogging();// 执行查询时自动输出日志
await db.select('users', ['id', 'name']).where({ status: 'active' });
// [SQL] SELECT id, name FROM users WHERE status = ? ["active"] → ✅ 3ms
await db.insert('logs', { message: 'test' });
// [SQL] INSERT INTO logs (message) VALUES (?) ["test"] → ✅ 1ms
// 错误时也会记录
await db.query('SELECT * FROM not_exist');
// [SQL] SELECT * FROM not_exist → ❌ ERROR: no such table: not_exist
// 自定义日志处理器
db.enableLogging(({ sql, params, duration, error }) => {
if (error) {
logger.error(
Query failed: ${sql}, { params, error });
} else if (duration > 100) {
logger.warn(Slow query: ${sql}, { params, duration });
} else {
logger.debug(Query: ${sql}, { params, duration });
}
});// 禁用日志
db.disableLogging();
// 检查日志状态
if (db.isLogging) {
console.log('Query logging is enabled');
}
`$3
MySQL 和 PostgreSQL 支持连接池,提高高并发场景下的性能:
`typescript
// MySQL 连接池配置
const db = Registry.create('mysql', {
host: 'localhost',
port: 3306,
user: 'root',
password: 'password',
database: 'myapp',
pool: {
min: 2, // 最小连接数
max: 10, // 最大连接数
idleTimeoutMillis: 30000, // 空闲连接超时(毫秒)
acquireTimeoutMillis: 10000 // 获取连接超时(毫秒)
}
}, schemas);// PostgreSQL 连接池配置
const db = Registry.create('pg', {
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'password',
database: 'myapp',
pool: {
min: 2,
max: 10,
idleTimeoutMillis: 30000,
acquireTimeoutMillis: 10000
}
}, schemas);
await db.start();
// 连接池模式下的事务会自动获取专用连接
await db.transaction(async (trx) => {
// 这个事务使用连接池中的一个专用连接
await trx.query('...');
});
// 事务结束后连接自动归还到池中
`连接池 vs 单连接:
| 特性 | 单连接 | 连接池 |
|------|--------|--------|
| 适用场景 | 低并发、简单应用 | 高并发、生产环境 |
| 连接数 | 1 | 可配置 (min-max) |
| 事务隔离 | 自然隔离 | 自动获取专用连接 |
| 资源利用 | 简单 | 高效复用 |
关联关系 (Relations)
支持定义和查询模型之间的关联关系,解决 N+1 查询问题。
$3
类似 Sequelize,在数据库层面一次性定义所有关系:
`typescript
const db = Registry.create('sqlite', { filename: ':memory:' });// 预定义关系配置
db.defineRelations({
users: {
hasMany: { orders: 'userId', posts: 'authorId' },
hasOne: { profile: 'userId' }
},
orders: {
belongsTo: { users: 'userId' }
},
posts: {
belongsTo: { users: 'authorId' }
}
});
// 获取模型时自动应用关系,无需手动调用 hasMany/belongsTo
const userModel = db.model('users');
const usersWithOrders = await userModel.with('orders', 'profile');
`$3
`typescript
const userModel = db.model('users');
const orderModel = db.model('orders');
const profileModel = db.model('profiles');// 一对多: User hasMany Orders (orders.userId -> users.id)
userModel.hasMany(orderModel, 'userId');
// 多对一: Order belongsTo User (orders.userId -> users.id)
orderModel.belongsTo(userModel, 'userId');
// 一对一: User hasOne Profile (profiles.userId -> users.id)
userModel.hasOne(profileModel, 'userId');
`类型安全:传入模型实例而不是字符串,确保外键字段名正确:
`typescript
// ✅ 类型检查通过
orderModel.belongsTo(userModel, 'userId');// ❌ 类型错误:'wrongKey' 不存在于 orders 表
orderModel.belongsTo(userModel, 'wrongKey');
`$3
`typescript
const user = await userModel.selectById(1);
const userWithOrders = await userModel.loadRelation(user, 'orders');console.log(userWithOrders.orders);
// [{ id: 1, productName: 'A' }, { id: 2, productName: 'B' }]
`$3
`typescript
const users = await userModel.select();
const usersWithOrders = await userModel.loadRelations(users, ['orders']);// 只执行 2 次查询:
// 1. SELECT * FROM users
// 2. SELECT * FROM orders WHERE userId IN (1, 2, 3...)
`$3
`typescript
const usersWithOrders = await userModel.with('orders')
.where({ status: 'active' })
.orderBy('id', 'ASC')
.limit(10);// 每个 user 都自动带有 orders 数组
usersWithOrders.forEach(user => {
console.log(
${user.name} has ${user.orders.length} orders);
});
`$3
| 方法 | 描述 | 返回值 |
|------|------|--------|
|
hasMany(targetModel, foreignKey) | 一对多 | T[] |
| belongsTo(targetModel, foreignKey) | 多对一 | T \| null |
| hasOne(targetModel, foreignKey) | 一对一 | T \| null |
| belongsToMany(targetModel, pivot, fk, rk) | 多对多 | T[] |$3
多对多关系需要一个中间表(pivot table)来存储两个表之间的关联。
#### 基本用法
`typescript
const userModel = db.model('users');
const roleModel = db.model('roles');// User belongsToMany Roles (通过 user_roles 中间表)
// user_roles 表结构: { user_id, role_id }
userModel.belongsToMany(
roleModel, // 目标模型
'user_roles', // 中间表名
'user_id', // 中间表中指向本表的外键
'role_id' // 中间表中指向目标表的外键
);
// 双向关系
roleModel.belongsToMany(userModel, 'user_roles', 'role_id', 'user_id');
`#### 加载关联数据
`typescript
// 单条记录加载
const user = await userModel.findById(1);
const userWithRoles = await userModel.loadRelation(user, 'roles');
console.log(userWithRoles.roles); // [{ id: 1, name: 'admin' }, { id: 2, name: 'editor' }]// 批量加载(with)
const usersWithRoles = await userModel.with('roles');
usersWithRoles.forEach(user => {
console.log(
${user.name} has roles: ${user.roles.map(r => r.name).join(', ')});
});
`#### 访问中间表数据
如果中间表有额外字段(如
created_at、sort_order 等),可以通过 pivotFields 参数获取:`typescript
// 中间表: post_tags { post_id, tag_id, sort_order }
postModel.belongsToMany(
tagModel,
'post_tags',
'post_id',
'tag_id',
'id', // 本表主键
'id', // 目标表主键
['sort_order'] // 需要获取的中间表字段
);const postWithTags = await postModel.loadRelation(post, 'tags');
postWithTags.tags.forEach(tag => {
console.log(
${tag.name} - sort: ${tag.pivot.sort_order});
});
`#### Schema 预定义多对多关系
`typescript
db.defineRelations({
users: {
belongsToMany: {
roles: {
pivot: 'user_roles',
foreignKey: 'user_id',
relatedKey: 'role_id',
pivotFields: ['assigned_at'] // 可选
}
}
},
roles: {
belongsToMany: {
users: {
pivot: 'user_roles',
foreignKey: 'role_id',
relatedKey: 'user_id'
}
}
}
});
`生命周期钩子 (Lifecycle Hooks)
在 CRUD 操作的关键节点执行自定义逻辑。
$3
| 钩子名称 | 触发时机 | 可取消操作 |
|---------|---------|-----------|
|
beforeCreate | 创建前 | ✅ 返回 false |
| afterCreate | 创建后 | - |
| beforeFind | 查询前 | ✅ 返回 false |
| afterFind | 查询后 | - |
| beforeUpdate | 更新前 | ✅ 返回 false |
| afterUpdate | 更新后 | - |
| beforeDelete | 删除前 | ✅ 返回 false |
| afterDelete | 删除后 | - |$3
`typescript
const userModel = db.model('users');// 方式一:addHook(链式调用)
userModel
.addHook('beforeCreate', (ctx) => {
// 自动生成 slug
ctx.data.slug = slugify(ctx.data.name);
})
.addHook('afterCreate', async (ctx) => {
// 记录日志
await logService.log('User created', ctx.result);
});
// 方式二:on(别名)
userModel.on('beforeDelete', (ctx) => {
console.log('About to delete:', ctx.where);
});
// 方式三:批量注册
userModel.registerHooks({
beforeCreate: (ctx) => { / ... / },
afterUpdate: [
(ctx) => { / hook 1 / },
(ctx) => { / hook 2 / }
]
});
`$3
`typescript
interface HookContext {
modelName: string; // 模型名称
data?: Partial; // 创建/更新的数据
where?: Condition; // 查询/更新/删除条件
result?: T | T[] | number; // 操作结果(after 钩子)
}
`$3
before 钩子返回 false 可以取消操作:`typescript
userModel.addHook('beforeDelete', async (ctx) => {
// 禁止删除管理员
const user = await userModel.findOne(ctx.where);
if (user?.role === 'admin') {
return false; // 取消删除
}
});await userModel.deleteById(1); // 如果是管理员,返回 false
`$3
beforeCreate 和 beforeUpdate 可以修改数据:`typescript
userModel.addHook('beforeCreate', (ctx) => {
// 统一处理
ctx.data.status = 'pending';
ctx.data.name = ctx.data.name?.trim().toLowerCase();
});userModel.addHook('beforeUpdate', (ctx) => {
// 自动更新时间
ctx.data.updatedAt = new Date();
});
`$3
afterFind 可以转换查询结果:`typescript
userModel.addHook('afterFind', (ctx) => {
if (ctx.result && !Array.isArray(ctx.result)) {
// 添加计算属性
ctx.result.fullName = ${ctx.result.firstName} ${ctx.result.lastName};
}
});
`$3
`typescript
const myHook = (ctx) => { / ... / };
userModel.addHook('beforeCreate', myHook);// 移除特定钩子
userModel.removeHook('beforeCreate', myHook);
// 移除某类型的所有钩子
userModel.removeHook('beforeCreate');
// 清除所有钩子
userModel.clearHooks();
`$3
| 方法 | 触发的钩子 |
|------|-----------|
|
create(data) | beforeCreate → afterCreate |
| findOne(where) / findById(id) | beforeFind → afterFind |
| findAll(where) | beforeFind → afterFind |
| updateWhere(where, data) / updateById(id, data) | beforeUpdate → afterUpdate |
| deleteWhere(where) / deleteById(id) | beforeDelete → afterDelete |> 注意:直接使用
insert(), select(), update(), delete() 链式查询 不会 触发钩子,需要使用上述便捷方法。数据迁移 (Migration)
版本化的数据库结构变更管理,支持升级和回滚。
down 操作可自动生成,无需手动编写。$3
`typescript
import { MigrationRunner, defineMigration } from '@zhin.js/database';const runner = new MigrationRunner(db);
// 定义迁移 - 只需写 up,down 会自动生成!
runner.add(defineMigration({
name: '001_create_users',
up: async (ctx) => {
await ctx.createTable('users', {
id: { type: 'integer', primary: true, autoIncrement: true },
name: { type: 'text', nullable: false },
email: { type: 'text', unique: true },
createdAt: { type: 'date' }
});
}
// down 自动生成: ctx.dropTable('users')
}));
runner.add(defineMigration({
name: '002_add_user_status',
up: async (ctx) => {
await ctx.addColumn('users', 'status', { type: 'text', default: 'active' });
await ctx.addIndex('users', 'idx_status', ['status']);
}
// down 自动生成:
// ctx.dropIndex('users', 'idx_status')
// ctx.dropColumn('users', 'status')
}));
// 运行所有待执行的迁移
await runner.migrate();
`$3
系统会记录
up 中执行的操作,并自动生成反向操作:| up 操作 | 自动生成的 down 操作 |
|---------|---------------------|
|
createTable(name, columns) | dropTable(name) |
| addColumn(table, col, def) | dropColumn(table, col) |
| addIndex(table, idx, cols) | dropIndex(table, idx) |
| renameColumn(table, old, new) | renameColumn(table, new, old) |以下操作无法自动生成 down(需要手动提供):
-
dropTable - 需要原始表结构才能恢复
- dropColumn - 需要原始列定义才能恢复
- dropIndex - 需要原始索引定义才能恢复
- query - 原生 SQL 无法自动反向$3
对于复杂场景或无法自动反向的操作,可以显式提供
down:`typescript
runner.add(defineMigration({
name: '003_seed_settings',
up: async (ctx) => {
// 原生 SQL 无法自动反向
await ctx.query(INSERT INTO settings VALUES (?, ?), ['theme', 'dark']);
},
down: async (ctx) => {
await ctx.query(DELETE FROM settings WHERE key = ?, ['theme']);
}
}));
`$3
`typescript
// 运行所有待执行的迁移
const migrated = await runner.migrate();
console.log('已迁移:', migrated);// 回滚最后一批迁移
const rolledBack = await runner.rollback();
console.log('已回滚:', rolledBack);
// 回滚所有迁移
await runner.reset();
// 重新运行所有迁移 (reset + migrate)
const { rolledBack, migrated } = await runner.refresh();
// 查看迁移状态
const status = await runner.status();
// [
// { name: '001_create_users', status: 'executed', batch: 1 },
// { name: '002_add_status', status: 'pending' }
// ]
`$3
| 方法 | 描述 |
|------|------|
|
createTable(name, columns) | 创建表 |
| dropTable(name) | 删除表 |
| addColumn(table, column, definition) | 添加列 |
| dropColumn(table, column) | 删除列 |
| renameColumn(table, oldName, newName) | 重命名列 |
| addIndex(table, indexName, columns, unique?) | 添加索引 |
| dropIndex(table, indexName) | 删除索引 |
| query(sql, params?) | 执行原生 SQL |$3
`typescript
runner.addAll([
defineMigration({ name: '001_users', up: async (ctx) => { / ... / } }),
defineMigration({ name: '002_orders', up: async (ctx) => { / ... / } }),
defineMigration({ name: '003_products', up: async (ctx) => { / ... / } }),
]);// 同一批次的迁移会一起执行/回滚
await runner.migrate();
``MIT