@zhin.js/database 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +1360 -34
- package/lib/base/database.d.ts +71 -13
- package/lib/base/database.d.ts.map +1 -1
- package/lib/base/database.js +128 -4
- package/lib/base/database.js.map +1 -1
- package/lib/base/dialect.d.ts +27 -10
- package/lib/base/dialect.d.ts.map +1 -1
- package/lib/base/dialect.js +32 -0
- package/lib/base/dialect.js.map +1 -1
- package/lib/base/index.d.ts +1 -0
- package/lib/base/index.d.ts.map +1 -1
- package/lib/base/index.js +1 -0
- package/lib/base/index.js.map +1 -1
- package/lib/base/model.d.ts +105 -12
- package/lib/base/model.d.ts.map +1 -1
- package/lib/base/model.js +224 -3
- package/lib/base/model.js.map +1 -1
- package/lib/base/query-classes.d.ts +204 -33
- package/lib/base/query-classes.d.ts.map +1 -1
- package/lib/base/query-classes.js +276 -0
- package/lib/base/query-classes.js.map +1 -1
- package/lib/base/thenable.d.ts +7 -7
- package/lib/base/thenable.d.ts.map +1 -1
- package/lib/base/thenable.js +5 -4
- package/lib/base/thenable.js.map +1 -1
- package/lib/base/transaction.d.ts +46 -0
- package/lib/base/transaction.d.ts.map +1 -0
- package/lib/base/transaction.js +186 -0
- package/lib/base/transaction.js.map +1 -0
- package/lib/dialects/memory.d.ts +13 -8
- package/lib/dialects/memory.d.ts.map +1 -1
- package/lib/dialects/memory.js +10 -7
- package/lib/dialects/memory.js.map +1 -1
- package/lib/dialects/mongodb.d.ts +12 -8
- package/lib/dialects/mongodb.d.ts.map +1 -1
- package/lib/dialects/mongodb.js +21 -18
- package/lib/dialects/mongodb.js.map +1 -1
- package/lib/dialects/mysql.d.ts +36 -7
- package/lib/dialects/mysql.d.ts.map +1 -1
- package/lib/dialects/mysql.js +140 -21
- package/lib/dialects/mysql.js.map +1 -1
- package/lib/dialects/pg.d.ts +36 -7
- package/lib/dialects/pg.d.ts.map +1 -1
- package/lib/dialects/pg.js +140 -21
- package/lib/dialects/pg.js.map +1 -1
- package/lib/dialects/redis.d.ts +13 -8
- package/lib/dialects/redis.d.ts.map +1 -1
- package/lib/dialects/redis.js +14 -11
- package/lib/dialects/redis.js.map +1 -1
- package/lib/dialects/sqlite.d.ts +20 -7
- package/lib/dialects/sqlite.d.ts.map +1 -1
- package/lib/dialects/sqlite.js +66 -13
- package/lib/dialects/sqlite.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/migration.d.ts +132 -0
- package/lib/migration.d.ts.map +1 -0
- package/lib/migration.js +475 -0
- package/lib/migration.js.map +1 -0
- package/lib/registry.d.ts +26 -23
- package/lib/registry.d.ts.map +1 -1
- package/lib/registry.js +1 -5
- package/lib/registry.js.map +1 -1
- package/lib/type/document/database.d.ts +12 -12
- package/lib/type/document/database.d.ts.map +1 -1
- package/lib/type/document/database.js +1 -1
- package/lib/type/document/database.js.map +1 -1
- package/lib/type/document/model.d.ts +8 -8
- package/lib/type/document/model.d.ts.map +1 -1
- package/lib/type/document/model.js +1 -1
- package/lib/type/document/model.js.map +1 -1
- package/lib/type/keyvalue/database.d.ts +12 -12
- package/lib/type/keyvalue/database.d.ts.map +1 -1
- package/lib/type/keyvalue/database.js +1 -1
- package/lib/type/keyvalue/database.js.map +1 -1
- package/lib/type/keyvalue/model.d.ts +3 -3
- package/lib/type/keyvalue/model.d.ts.map +1 -1
- package/lib/type/keyvalue/model.js +1 -1
- package/lib/type/keyvalue/model.js.map +1 -1
- package/lib/type/related/database.d.ts +49 -14
- package/lib/type/related/database.d.ts.map +1 -1
- package/lib/type/related/database.js +259 -28
- package/lib/type/related/database.js.map +1 -1
- package/lib/type/related/model.d.ts +252 -16
- package/lib/type/related/model.d.ts.map +1 -1
- package/lib/type/related/model.js +648 -23
- package/lib/type/related/model.js.map +1 -1
- package/lib/types.d.ts +475 -37
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +6 -0
- package/lib/types.js.map +1 -1
- package/package.json +10 -5
- package/src/base/database.ts +168 -24
- package/src/base/dialect.ts +49 -10
- package/src/base/index.ts +2 -1
- package/src/base/model.ts +258 -18
- package/src/base/query-classes.ts +471 -63
- package/src/base/thenable.ts +12 -11
- package/src/base/transaction.ts +213 -0
- package/src/dialects/memory.ts +17 -16
- package/src/dialects/mongodb.ts +44 -42
- package/src/dialects/mysql.ts +155 -26
- package/src/dialects/pg.ts +152 -25
- package/src/dialects/redis.ts +45 -43
- package/src/dialects/sqlite.ts +77 -19
- package/src/index.ts +1 -2
- package/src/migration.ts +544 -0
- package/src/registry.ts +33 -33
- package/src/type/document/database.ts +33 -33
- package/src/type/document/model.ts +15 -15
- package/src/type/keyvalue/database.ts +33 -33
- package/src/type/keyvalue/model.ts +19 -19
- package/src/type/related/database.ts +309 -34
- package/src/type/related/model.ts +801 -34
- package/src/types.ts +559 -44
- package/tests/database.test.ts +1738 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zhin.js/database
|
|
2
2
|
|
|
3
|
-
Universal database
|
|
3
|
+
Universal database abstraction layer for Zhin.js framework with support for multiple database backends.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,14 +8,32 @@ Universal database driver for zhin framework.
|
|
|
8
8
|
npm install @zhin.js/database
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Install the database driver you need:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# For SQLite
|
|
15
|
+
npm install sqlite3
|
|
16
|
+
|
|
17
|
+
# For MySQL
|
|
18
|
+
npm install mysql2
|
|
19
|
+
|
|
20
|
+
# For PostgreSQL
|
|
21
|
+
npm install pg
|
|
22
|
+
|
|
23
|
+
# For MongoDB
|
|
24
|
+
npm install mongodb
|
|
25
|
+
|
|
26
|
+
# For Redis
|
|
27
|
+
npm install redis
|
|
28
|
+
```
|
|
29
|
+
|
|
11
30
|
## Quick Start
|
|
12
31
|
|
|
13
32
|
```typescript
|
|
14
|
-
import {
|
|
15
|
-
import '@zhin.js/driver-sqlite'; // 导入方言
|
|
33
|
+
import { Registry } from '@zhin.js/database';
|
|
16
34
|
|
|
17
|
-
//
|
|
18
|
-
const db =
|
|
35
|
+
// 创建数据库实例(以 SQLite 为例)
|
|
36
|
+
const db = Registry.create('sqlite', {
|
|
19
37
|
filename: './database.sqlite'
|
|
20
38
|
}, {
|
|
21
39
|
users: {
|
|
@@ -27,7 +45,7 @@ const db = Database.create('sqlite', {
|
|
|
27
45
|
|
|
28
46
|
await db.start();
|
|
29
47
|
|
|
30
|
-
//
|
|
48
|
+
// 使用模型进行 CRUD 操作
|
|
31
49
|
const userModel = db.model('users');
|
|
32
50
|
const user = await userModel.create({
|
|
33
51
|
name: 'John Doe',
|
|
@@ -37,56 +55,1364 @@ const user = await userModel.create({
|
|
|
37
55
|
|
|
38
56
|
## Supported Databases
|
|
39
57
|
|
|
40
|
-
### Relational Databases
|
|
41
|
-
- **SQLite
|
|
42
|
-
-
|
|
43
|
-
-
|
|
58
|
+
### ✅ Relational Databases (已完整实现)
|
|
59
|
+
- **SQLite** - 内置支持,需要安装 `sqlite3`
|
|
60
|
+
- 轻量级、零配置
|
|
61
|
+
- 适合中小型应用
|
|
62
|
+
- 支持 WAL 模式
|
|
63
|
+
|
|
64
|
+
- **MySQL** - 内置支持,需要安装 `mysql2`
|
|
65
|
+
- 完整的关系型数据库特性
|
|
66
|
+
- 高性能、可扩展
|
|
67
|
+
- 广泛使用
|
|
68
|
+
|
|
69
|
+
- **PostgreSQL** - 内置支持,需要安装 `pg`
|
|
70
|
+
- 强大的企业级数据库
|
|
71
|
+
- 支持高级 SQL 特性
|
|
72
|
+
- JSON 支持
|
|
73
|
+
|
|
74
|
+
### ✅ NoSQL Databases (已完整实现)
|
|
75
|
+
- **MongoDB** - 内置支持,需要安装 `mongodb`
|
|
76
|
+
- 文档型数据库
|
|
77
|
+
- 灵活的 Schema
|
|
78
|
+
- 适合非结构化数据
|
|
79
|
+
|
|
80
|
+
- **Redis** - 内置支持,需要安装 `redis`
|
|
81
|
+
- 键值存储
|
|
82
|
+
- 高性能缓存
|
|
83
|
+
- 支持多种数据结构
|
|
84
|
+
|
|
85
|
+
### ✅ In-Memory Database (已完整实现)
|
|
86
|
+
- **Memory** - 内置支持,无需额外安装
|
|
87
|
+
- 完全在内存中运行
|
|
88
|
+
- 适合测试和临时数据
|
|
89
|
+
- 零配置
|
|
90
|
+
|
|
91
|
+
## Usage Examples
|
|
44
92
|
|
|
45
|
-
###
|
|
46
|
-
- **MongoDB**: `@zhin.js/driver-mongodb`
|
|
47
|
-
- **Redis**: `@zhin.js/driver-redis`
|
|
93
|
+
### SQLite Example
|
|
48
94
|
|
|
49
|
-
|
|
50
|
-
|
|
95
|
+
```typescript
|
|
96
|
+
import { Registry } from '@zhin.js/database';
|
|
97
|
+
|
|
98
|
+
const db = Registry.create('sqlite', {
|
|
99
|
+
filename: './data/bot.db',
|
|
100
|
+
mode: 'wal' // Write-Ahead Logging 模式
|
|
101
|
+
}, {
|
|
102
|
+
users: {
|
|
103
|
+
id: { type: 'integer', primary: true, autoIncrement: true },
|
|
104
|
+
name: { type: 'string', nullable: false },
|
|
105
|
+
createdAt: { type: 'timestamp', default: 'CURRENT_TIMESTAMP' }
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await db.start();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### MySQL Example
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const db = Registry.create('mysql', {
|
|
116
|
+
host: 'localhost',
|
|
117
|
+
port: 3306,
|
|
118
|
+
user: 'root',
|
|
119
|
+
password: 'password',
|
|
120
|
+
database: 'myapp'
|
|
121
|
+
}, schemas);
|
|
122
|
+
|
|
123
|
+
await db.start();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### PostgreSQL Example
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const db = Registry.create('pg', {
|
|
130
|
+
host: 'localhost',
|
|
131
|
+
port: 5432,
|
|
132
|
+
user: 'postgres',
|
|
133
|
+
password: 'password',
|
|
134
|
+
database: 'myapp'
|
|
135
|
+
}, schemas);
|
|
136
|
+
|
|
137
|
+
await db.start();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### MongoDB Example
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const db = Registry.create('mongodb', {
|
|
144
|
+
url: 'mongodb://localhost:27017',
|
|
145
|
+
dbName: 'myapp'
|
|
146
|
+
}, schemas);
|
|
147
|
+
|
|
148
|
+
await db.start();
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Redis Example
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const db = Registry.create('redis', {
|
|
155
|
+
socket: {
|
|
156
|
+
host: 'localhost',
|
|
157
|
+
port: 6379
|
|
158
|
+
}
|
|
159
|
+
}, schemas);
|
|
160
|
+
|
|
161
|
+
await db.start();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Memory Example (for Testing)
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const db = Registry.create('memory', {}, schemas);
|
|
168
|
+
await db.start();
|
|
169
|
+
```
|
|
51
170
|
|
|
52
171
|
## Database Types
|
|
53
172
|
|
|
54
173
|
### RelatedDatabase
|
|
55
|
-
|
|
174
|
+
适用于关系型数据库 (SQLite, MySQL, PostgreSQL)
|
|
175
|
+
- 支持 SQL 查询
|
|
176
|
+
- 支持事务
|
|
177
|
+
- 支持索引和约束
|
|
178
|
+
|
|
179
|
+
### DocumentDatabase
|
|
180
|
+
适用于文档型数据库 (MongoDB)
|
|
181
|
+
- 灵活的 Schema
|
|
182
|
+
- 支持嵌套文档
|
|
183
|
+
- 支持丰富的查询操作
|
|
184
|
+
|
|
185
|
+
### KeyValueDatabase
|
|
186
|
+
适用于键值存储 (Redis)
|
|
187
|
+
- 高性能读写
|
|
188
|
+
- 支持多种数据结构
|
|
189
|
+
- 支持过期时间
|
|
190
|
+
|
|
191
|
+
## Features
|
|
192
|
+
|
|
193
|
+
### ✨ 核心特性
|
|
194
|
+
- **🎯 类型安全**: 完整的 TypeScript 类型支持
|
|
195
|
+
- **🔄 统一 API**: 所有数据库类型使用相同的接口
|
|
196
|
+
- **🔍 查询构建器**: 流畅的链式查询 API
|
|
197
|
+
- **📋 Schema 管理**: 自动创建表/集合
|
|
198
|
+
- **🔌 连接管理**: 自动处理连接和重连
|
|
199
|
+
- **💾 事务支持**: 内置事务支持(关系型数据库)
|
|
200
|
+
- **🔄 迁移支持**: Schema 演进和版本管理
|
|
201
|
+
|
|
202
|
+
### 📦 开箱即用
|
|
203
|
+
- 无需额外配置即可使用
|
|
204
|
+
- 自动检测并安装相应的数据库驱动
|
|
205
|
+
- 完整的错误处理和日志记录
|
|
206
|
+
|
|
207
|
+
### 🚀 高性能
|
|
208
|
+
- 连接池管理
|
|
209
|
+
- 查询优化
|
|
210
|
+
- 批量操作支持
|
|
211
|
+
|
|
212
|
+
## Model API
|
|
56
213
|
|
|
57
214
|
```typescript
|
|
58
|
-
|
|
215
|
+
const model = db.model('users');
|
|
216
|
+
|
|
217
|
+
// Create
|
|
218
|
+
const user = await model.create({ name: 'John', email: 'john@example.com' });
|
|
219
|
+
|
|
220
|
+
// Read
|
|
221
|
+
const users = await model.find({ name: 'John' });
|
|
222
|
+
const user = await model.findOne({ email: 'john@example.com' });
|
|
223
|
+
|
|
224
|
+
// Update
|
|
225
|
+
await model.update({ name: 'John' }, { name: 'Jane' });
|
|
226
|
+
|
|
227
|
+
// Delete
|
|
228
|
+
await model.remove({ name: 'Jane' });
|
|
59
229
|
|
|
60
|
-
|
|
230
|
+
// Count
|
|
231
|
+
const count = await model.count({ email: { $like: '%@example.com' } });
|
|
232
|
+
|
|
233
|
+
// Pagination
|
|
234
|
+
const result = await model.find({}, { limit: 10, offset: 0 });
|
|
61
235
|
```
|
|
62
236
|
|
|
63
|
-
|
|
64
|
-
|
|
237
|
+
## 链式查询 API (Fluent Query Builder)
|
|
238
|
+
|
|
239
|
+
链式查询提供了一种流畅、类型安全的方式来构建数据库查询。所有查询都是 **Thenable** 的,可以直接使用 `await` 或 `.then()` 执行。
|
|
240
|
+
|
|
241
|
+
### 基本用法
|
|
65
242
|
|
|
66
243
|
```typescript
|
|
67
|
-
|
|
244
|
+
// 从数据库实例使用 - select(表名, 字段数组)
|
|
245
|
+
const users = await db
|
|
246
|
+
.select('users', ['id', 'name', 'email'])
|
|
247
|
+
.where({ status: 'active' })
|
|
248
|
+
.orderBy('createdAt', 'DESC')
|
|
249
|
+
.limit(10);
|
|
68
250
|
|
|
69
|
-
|
|
251
|
+
// 从模型实例使用 - select(...字段) 展开参数
|
|
252
|
+
const model = db.model('users');
|
|
253
|
+
const users = await model
|
|
254
|
+
.select('id', 'name', 'email') // 展开参数,不是数组
|
|
255
|
+
.where({ status: 'active' })
|
|
256
|
+
.orderBy('createdAt', 'DESC')
|
|
257
|
+
.limit(10);
|
|
258
|
+
|
|
259
|
+
// 选择所有字段
|
|
260
|
+
const allUsers = await model.select();
|
|
70
261
|
```
|
|
71
262
|
|
|
72
|
-
###
|
|
73
|
-
For key-value stores (Redis)
|
|
263
|
+
### Select 查询
|
|
74
264
|
|
|
75
265
|
```typescript
|
|
76
|
-
|
|
266
|
+
// 从数据库实例:select(表名, 字段数组)
|
|
267
|
+
const users = await db
|
|
268
|
+
.select('users', ['id', 'name', 'email'])
|
|
269
|
+
.where({ age: { $gte: 18 } })
|
|
270
|
+
.groupBy('department')
|
|
271
|
+
.orderBy('name', 'ASC')
|
|
272
|
+
.limit(20)
|
|
273
|
+
.offset(0);
|
|
274
|
+
|
|
275
|
+
// 从模型实例:select(...字段)
|
|
276
|
+
const model = db.model('users');
|
|
277
|
+
const users = await model
|
|
278
|
+
.select('id', 'name', 'email') // 展开参数
|
|
279
|
+
.where({ age: { $gte: 18 } })
|
|
280
|
+
.groupBy('department')
|
|
281
|
+
.orderBy('name', 'ASC')
|
|
282
|
+
.limit(20)
|
|
283
|
+
.offset(0);
|
|
77
284
|
|
|
78
|
-
|
|
285
|
+
// 链式方法说明
|
|
286
|
+
// .where(condition) - 添加查询条件
|
|
287
|
+
// .groupBy(...fields) - 分组字段
|
|
288
|
+
// .orderBy(field, dir) - 排序(ASC/DESC)
|
|
289
|
+
// .limit(count) - 限制返回数量
|
|
290
|
+
// .offset(count) - 跳过指定数量
|
|
79
291
|
```
|
|
80
292
|
|
|
81
|
-
|
|
293
|
+
### Insert 插入
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// 从数据库实例
|
|
297
|
+
const newUser = await db.insert('users', {
|
|
298
|
+
name: 'Alice',
|
|
299
|
+
email: 'alice@example.com',
|
|
300
|
+
age: 25
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// 从模型实例
|
|
304
|
+
const model = db.model('users');
|
|
305
|
+
const newUser = await model.insert({
|
|
306
|
+
name: 'Alice',
|
|
307
|
+
email: 'alice@example.com',
|
|
308
|
+
age: 25
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Update 更新
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// 从数据库实例:update(表名, 更新数据).where(条件)
|
|
316
|
+
const affectedRows = await db
|
|
317
|
+
.update('users', { status: 'inactive' })
|
|
318
|
+
.where({ lastLogin: { $lt: new Date('2024-01-01') } });
|
|
319
|
+
|
|
320
|
+
// 从模型实例:update(更新数据).where(条件)
|
|
321
|
+
const model = db.model('users');
|
|
322
|
+
const count = await model
|
|
323
|
+
.update({ verified: true })
|
|
324
|
+
.where({
|
|
325
|
+
email: { $like: '%@company.com' },
|
|
326
|
+
status: 'pending'
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Delete 删除
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// 从数据库实例:delete(表名, 初始条件).where(额外条件)
|
|
334
|
+
const deletedUsers = await db
|
|
335
|
+
.delete('users', { status: 'deleted' })
|
|
336
|
+
.where({ deletedAt: { $lt: new Date('2023-01-01') } });
|
|
337
|
+
|
|
338
|
+
// 从模型实例:delete(条件)
|
|
339
|
+
const model = db.model('users');
|
|
340
|
+
const deleted = await model.delete({ status: 'banned' });
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Alter 修改表结构
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// 从数据库实例
|
|
347
|
+
await db.alter('users', {
|
|
348
|
+
avatar: { action: 'add', type: 'string', nullable: true },
|
|
349
|
+
oldField: { action: 'drop' },
|
|
350
|
+
name: { action: 'modify', type: 'string', nullable: false }
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// 从模型实例
|
|
354
|
+
const model = db.model('users');
|
|
355
|
+
await model.alter({
|
|
356
|
+
newColumn: { action: 'add', type: 'integer', default: 0 }
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 模型便捷方法 (RelatedModel)
|
|
361
|
+
|
|
362
|
+
关系型模型提供了更便捷的高级方法:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const model = db.model('users'); // RelatedModel
|
|
366
|
+
|
|
367
|
+
// create - 创建单条数据
|
|
368
|
+
const user = await model.create({ name: 'John', email: 'john@example.com' });
|
|
369
|
+
|
|
370
|
+
// createMany - 批量创建
|
|
371
|
+
const users = await model.createMany([
|
|
372
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
373
|
+
{ name: 'Bob', email: 'bob@example.com' }
|
|
374
|
+
]);
|
|
375
|
+
|
|
376
|
+
// selectOne - 查找单条数据
|
|
377
|
+
const user = await model.selectOne({ email: 'john@example.com' });
|
|
378
|
+
|
|
379
|
+
// selectById - 根据 ID 查找
|
|
380
|
+
const user = await model.selectById(1);
|
|
381
|
+
|
|
382
|
+
// updateOne - 更新单条数据
|
|
383
|
+
const success = await model.updateOne({ id: 1 }, { name: 'Jane' });
|
|
384
|
+
|
|
385
|
+
// updateById - 根据 ID 更新
|
|
386
|
+
const success = await model.updateById(1, { status: 'active' });
|
|
387
|
+
|
|
388
|
+
// deleteById - 根据 ID 删除
|
|
389
|
+
const success = await model.deleteById(1);
|
|
390
|
+
|
|
391
|
+
// count - 统计数量
|
|
392
|
+
const total = await model.count({ status: 'active' });
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 文档模型便捷方法 (DocumentModel)
|
|
396
|
+
|
|
397
|
+
文档型模型(MongoDB)的特有方法:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
const model = db.model('users'); // DocumentModel
|
|
401
|
+
|
|
402
|
+
// create - 创建文档(自动生成 _id)
|
|
403
|
+
const user = await model.create({ name: 'John', email: 'john@example.com' });
|
|
404
|
+
// 返回: { name: 'John', email: '...', _id: 'abc123...' }
|
|
405
|
+
|
|
406
|
+
// 批量创建
|
|
407
|
+
const users = await model.create([
|
|
408
|
+
{ name: 'Alice' },
|
|
409
|
+
{ name: 'Bob' }
|
|
410
|
+
]);
|
|
411
|
+
|
|
412
|
+
// selectOne - 查找单个文档
|
|
413
|
+
const user = await model.selectOne('name', 'email');
|
|
414
|
+
|
|
415
|
+
// selectById - 根据 _id 查找
|
|
416
|
+
const user = await model.selectById('abc123...');
|
|
417
|
+
|
|
418
|
+
// updateById - 根据 _id 更新
|
|
419
|
+
await model.updateById('abc123...', { name: 'Jane' });
|
|
420
|
+
|
|
421
|
+
// deleteById - 根据 _id 删除
|
|
422
|
+
await model.deleteById('abc123...');
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### 键值模型方法 (KeyValueModel)
|
|
426
|
+
|
|
427
|
+
键值存储(Redis)的特有方法:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const model = db.model('cache'); // KeyValueModel
|
|
431
|
+
|
|
432
|
+
// set/get - 基本键值操作
|
|
433
|
+
await model.set('user:1', { name: 'John', age: 25 });
|
|
434
|
+
const user = await model.get('user:1');
|
|
435
|
+
|
|
436
|
+
// 带过期时间(秒)
|
|
437
|
+
await model.set('session:abc', { userId: 1 }, 3600); // 1小时后过期
|
|
438
|
+
|
|
439
|
+
// has - 检查键是否存在
|
|
440
|
+
const exists = await model.has('user:1');
|
|
441
|
+
|
|
442
|
+
// deleteByKey - 删除键
|
|
443
|
+
await model.deleteByKey('user:1');
|
|
444
|
+
|
|
445
|
+
// keys/values/entries - 遍历
|
|
446
|
+
const allKeys = await model.keys();
|
|
447
|
+
const allValues = await model.values();
|
|
448
|
+
const allEntries = await model.entries();
|
|
449
|
+
|
|
450
|
+
// 模式匹配查找
|
|
451
|
+
const userKeys = await model.keysByPattern('user:*');
|
|
452
|
+
|
|
453
|
+
// TTL 操作
|
|
454
|
+
await model.expire('session:abc', 1800); // 设置过期时间
|
|
455
|
+
const ttl = await model.ttl('session:abc'); // 获取剩余时间
|
|
456
|
+
await model.persist('session:abc'); // 移除过期时间
|
|
457
|
+
|
|
458
|
+
// 原子操作
|
|
459
|
+
await model.setIfNotExists('lock:resource', 'locked', 30);
|
|
460
|
+
await model.setIfExists('counter', newValue);
|
|
461
|
+
const oldValue = await model.getAndSet('key', newValue);
|
|
462
|
+
const value = await model.deleteAndGet('key');
|
|
463
|
+
|
|
464
|
+
// 批量操作
|
|
465
|
+
await model.setMany([['key1', 'value1'], ['key2', 'value2']], 3600);
|
|
466
|
+
|
|
467
|
+
// 清理
|
|
468
|
+
await model.clear(); // 清空所有键
|
|
469
|
+
await model.cleanup(); // 清理过期键
|
|
470
|
+
const size = await model.size(); // 获取键数量
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 条件操作符
|
|
474
|
+
|
|
475
|
+
#### 比较操作符
|
|
476
|
+
|
|
477
|
+
| 操作符 | 说明 | 示例 |
|
|
478
|
+
|--------|------|------|
|
|
479
|
+
| `$eq` | 等于 | `{ age: { $eq: 18 } }` |
|
|
480
|
+
| `$ne` | 不等于 | `{ status: { $ne: 'deleted' } }` |
|
|
481
|
+
| `$gt` | 大于 | `{ age: { $gt: 18 } }` |
|
|
482
|
+
| `$gte` | 大于等于 | `{ age: { $gte: 18 } }` |
|
|
483
|
+
| `$lt` | 小于 | `{ age: { $lt: 65 } }` |
|
|
484
|
+
| `$lte` | 小于等于 | `{ age: { $lte: 65 } }` |
|
|
485
|
+
| `$in` | 在列表中 | `{ role: { $in: ['admin', 'mod'] } }` |
|
|
486
|
+
| `$nin` | 不在列表中 | `{ status: { $nin: ['banned', 'deleted'] } }` |
|
|
487
|
+
| `$like` | 模糊匹配 | `{ email: { $like: '%@gmail.com' } }` |
|
|
488
|
+
| `$nlike` | 不匹配 | `{ name: { $nlike: 'test%' } }` |
|
|
489
|
+
|
|
490
|
+
#### 逻辑操作符
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// $and - 逻辑与
|
|
494
|
+
const users = await model
|
|
495
|
+
.select('id', 'name')
|
|
496
|
+
.where({
|
|
497
|
+
$and: [
|
|
498
|
+
{ age: { $gte: 18 } },
|
|
499
|
+
{ status: 'active' }
|
|
500
|
+
]
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// $or - 逻辑或
|
|
504
|
+
const users = await model
|
|
505
|
+
.select('id', 'name')
|
|
506
|
+
.where({
|
|
507
|
+
$or: [
|
|
508
|
+
{ role: 'admin' },
|
|
509
|
+
{ role: 'moderator' }
|
|
510
|
+
]
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// $not - 逻辑非
|
|
514
|
+
const users = await model
|
|
515
|
+
.select('id', 'name')
|
|
516
|
+
.where({
|
|
517
|
+
$not: { status: 'banned' }
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// 组合使用
|
|
521
|
+
const users = await model
|
|
522
|
+
.select('id', 'name', 'email')
|
|
523
|
+
.where({
|
|
524
|
+
$and: [
|
|
525
|
+
{ age: { $gte: 18, $lte: 65 } },
|
|
526
|
+
{
|
|
527
|
+
$or: [
|
|
528
|
+
{ role: 'admin' },
|
|
529
|
+
{ verified: true }
|
|
530
|
+
]
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
});
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Thenable 特性
|
|
537
|
+
|
|
538
|
+
所有查询对象都实现了 `PromiseLike` 接口,支持多种异步调用方式:
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
// 使用 await
|
|
542
|
+
const users = await model.select('id', 'name').where({ active: true });
|
|
543
|
+
|
|
544
|
+
// 使用 .then()
|
|
545
|
+
model.select('id', 'name')
|
|
546
|
+
.where({ active: true })
|
|
547
|
+
.then(users => console.log(users));
|
|
548
|
+
|
|
549
|
+
// 使用 .catch() 处理错误
|
|
550
|
+
model.select('id', 'name')
|
|
551
|
+
.where({ active: true })
|
|
552
|
+
.catch(err => console.error(err));
|
|
553
|
+
|
|
554
|
+
// 使用 .finally()
|
|
555
|
+
model.select('id', 'name')
|
|
556
|
+
.where({ active: true })
|
|
557
|
+
.finally(() => console.log('Query completed'));
|
|
558
|
+
|
|
559
|
+
// 异步迭代器
|
|
560
|
+
for await (const user of model.select('id', 'name').where({ active: true })) {
|
|
561
|
+
console.log(user);
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### 完整示例
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
import { Registry } from '@zhin.js/database';
|
|
569
|
+
|
|
570
|
+
// 创建数据库
|
|
571
|
+
const db = Registry.create('sqlite', { filename: './app.db' }, {
|
|
572
|
+
users: {
|
|
573
|
+
id: { type: 'integer', primary: true, autoIncrement: true },
|
|
574
|
+
name: { type: 'string', nullable: false },
|
|
575
|
+
email: { type: 'string', unique: true },
|
|
576
|
+
age: { type: 'integer' },
|
|
577
|
+
role: { type: 'string', default: 'user' },
|
|
578
|
+
status: { type: 'string', default: 'active' },
|
|
579
|
+
createdAt: { type: 'timestamp', default: 'CURRENT_TIMESTAMP' }
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
await db.start();
|
|
584
|
+
|
|
585
|
+
// 获取模型
|
|
586
|
+
const userModel = db.model('users');
|
|
587
|
+
|
|
588
|
+
// 使用便捷方法创建数据
|
|
589
|
+
const user = await userModel.create({
|
|
590
|
+
name: 'John Doe',
|
|
591
|
+
email: 'john@example.com',
|
|
592
|
+
age: 28,
|
|
593
|
+
role: 'admin'
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// 使用链式查询
|
|
597
|
+
const activeAdmins = await userModel
|
|
598
|
+
.select('id', 'name', 'email')
|
|
599
|
+
.where({
|
|
600
|
+
$and: [
|
|
601
|
+
{ role: { $in: ['admin', 'moderator'] } },
|
|
602
|
+
{ status: 'active' },
|
|
603
|
+
{ age: { $gte: 21 } }
|
|
604
|
+
]
|
|
605
|
+
})
|
|
606
|
+
.orderBy('name', 'ASC')
|
|
607
|
+
.limit(10);
|
|
608
|
+
|
|
609
|
+
// 使用便捷方法更新
|
|
610
|
+
await userModel.updateOne(
|
|
611
|
+
{ role: 'guest', createdAt: { $lt: new Date('2024-01-01') } },
|
|
612
|
+
{ status: 'inactive' }
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
// 或使用链式更新
|
|
616
|
+
await userModel
|
|
617
|
+
.update({ status: 'inactive' })
|
|
618
|
+
.where({ role: 'guest' });
|
|
619
|
+
|
|
620
|
+
// 使用便捷方法删除
|
|
621
|
+
await userModel.deleteById(1);
|
|
622
|
+
|
|
623
|
+
// 或使用链式删除
|
|
624
|
+
await userModel.delete({ status: 'banned' });
|
|
625
|
+
|
|
626
|
+
// 统计
|
|
627
|
+
const count = await userModel.count({ status: 'active' });
|
|
628
|
+
console.log(`Active users: ${count}`);
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
## 高级功能
|
|
632
|
+
|
|
633
|
+
### 聚合查询
|
|
634
|
+
|
|
635
|
+
支持 COUNT, SUM, AVG, MIN, MAX 等聚合函数:
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// 基本聚合
|
|
639
|
+
const result = await db.aggregate('orders')
|
|
640
|
+
.count('*', 'total_orders')
|
|
641
|
+
.sum('amount', 'total_amount')
|
|
642
|
+
.avg('amount', 'avg_amount')
|
|
643
|
+
.max('amount', 'max_amount')
|
|
644
|
+
.min('amount', 'min_amount');
|
|
645
|
+
|
|
646
|
+
// 带条件和分组
|
|
647
|
+
const stats = await model.aggregate()
|
|
648
|
+
.count('*', 'count')
|
|
649
|
+
.sum('amount', 'total')
|
|
650
|
+
.where({ status: 'completed' })
|
|
651
|
+
.groupBy('category')
|
|
652
|
+
.having({ count: { $gt: 10 } });
|
|
653
|
+
|
|
654
|
+
// 结果示例
|
|
655
|
+
// [
|
|
656
|
+
// { category: 'electronics', count: 150, total: 50000 },
|
|
657
|
+
// { category: 'clothing', count: 80, total: 12000 }
|
|
658
|
+
// ]
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### 批量插入
|
|
662
|
+
|
|
663
|
+
高效的批量插入,生成单条 SQL 语句:
|
|
664
|
+
|
|
665
|
+
```typescript
|
|
666
|
+
// 批量插入多条记录
|
|
667
|
+
const result = await db.insertMany('users', [
|
|
668
|
+
{ name: 'Alice', email: 'alice@example.com', age: 25 },
|
|
669
|
+
{ name: 'Bob', email: 'bob@example.com', age: 30 },
|
|
670
|
+
{ name: 'Charlie', email: 'charlie@example.com', age: 35 }
|
|
671
|
+
]);
|
|
672
|
+
|
|
673
|
+
// 从 Model 调用
|
|
674
|
+
await model.insertMany([
|
|
675
|
+
{ name: 'User1', status: 'active' },
|
|
676
|
+
{ name: 'User2', status: 'active' },
|
|
677
|
+
{ name: 'User3', status: 'pending' }
|
|
678
|
+
]);
|
|
679
|
+
// 返回: { affectedRows: 3, insertIds: [...] }
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### 事务支持
|
|
683
|
+
|
|
684
|
+
支持 SQLite, MySQL, PostgreSQL 的事务操作,**支持链式调用**:
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
await db.transaction(async (trx) => {
|
|
688
|
+
// 插入
|
|
689
|
+
await trx.insert('orders', { userId, amount: 100 });
|
|
690
|
+
|
|
691
|
+
// 更新(支持链式 where)
|
|
692
|
+
await trx.update('accounts', { balance: newBalance })
|
|
693
|
+
.where({ userId });
|
|
694
|
+
|
|
695
|
+
// 查询(支持链式 where/orderBy/limit/offset)
|
|
696
|
+
const user = await trx.select('users', ['id', 'name'])
|
|
697
|
+
.where({ id: userId })
|
|
698
|
+
.orderBy('createdAt', 'DESC')
|
|
699
|
+
.limit(10);
|
|
700
|
+
|
|
701
|
+
// 删除(支持链式 where)
|
|
702
|
+
await trx.delete('temp_data')
|
|
703
|
+
.where({ expired: true });
|
|
704
|
+
|
|
705
|
+
// 批量插入
|
|
706
|
+
await trx.insertMany('logs', [
|
|
707
|
+
{ message: 'Order created', userId },
|
|
708
|
+
{ message: 'Balance updated', userId }
|
|
709
|
+
]);
|
|
710
|
+
|
|
711
|
+
// 也支持原生 SQL
|
|
712
|
+
await trx.query('UPDATE stats SET count = count + 1 WHERE type = ?', ['orders']);
|
|
713
|
+
|
|
714
|
+
// 如果所有操作成功,自动 commit
|
|
715
|
+
// 如果任何操作失败,自动 rollback
|
|
716
|
+
}, {
|
|
717
|
+
isolationLevel: 'REPEATABLE_READ' // 可选: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
|
|
718
|
+
});
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
**事务中支持的链式方法:**
|
|
722
|
+
|
|
723
|
+
| 方法 | 说明 | 链式方法 |
|
|
724
|
+
|------|------|----------|
|
|
725
|
+
| `trx.insert(table, data)` | 插入单条 | - |
|
|
726
|
+
| `trx.insertMany(table, data[])` | 批量插入 | - |
|
|
727
|
+
| `trx.select(table, fields)` | 查询 | `.where()`, `.orderBy()`, `.limit()`, `.offset()` |
|
|
728
|
+
| `trx.update(table, data)` | 更新 | `.where()` |
|
|
729
|
+
| `trx.delete(table)` | 删除 | `.where()` |
|
|
730
|
+
| `trx.query(sql, params)` | 原生 SQL | - |
|
|
731
|
+
|
|
732
|
+
### JOIN 关联查询
|
|
733
|
+
|
|
734
|
+
支持 `INNER JOIN`、`LEFT JOIN`、`RIGHT JOIN`,**返回类型自动推断**:
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
interface Schema {
|
|
738
|
+
users: { id: number; name: string; status: string };
|
|
739
|
+
orders: { id: number; userId: number; amount: number };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// INNER JOIN - 返回类型自动扩展
|
|
743
|
+
const result = await db.select('users', ['id', 'name'])
|
|
744
|
+
.join('orders', 'id', 'userId')
|
|
745
|
+
.where({ status: 'active' });
|
|
746
|
+
|
|
747
|
+
// ✅ 类型推断正确!
|
|
748
|
+
// result 类型: { users: { id: number; name: string }, orders: { id: number; userId: number; amount: number } }[]
|
|
749
|
+
result[0].users.id; // number ✅
|
|
750
|
+
result[0].users.name; // string ✅
|
|
751
|
+
result[0].orders.amount; // number ✅
|
|
752
|
+
|
|
753
|
+
// LEFT JOIN - 右表可能为 null
|
|
754
|
+
const leftResult = await db.select('users', ['id', 'name'])
|
|
755
|
+
.leftJoin('orders', 'id', 'userId');
|
|
756
|
+
// leftResult 类型: { users: {...}, orders: {...} | null }[]
|
|
757
|
+
leftResult[0].orders?.amount; // number | undefined ✅
|
|
758
|
+
|
|
759
|
+
// RIGHT JOIN - 左表可能为 null
|
|
760
|
+
const rightResult = await db.select('users', ['id', 'name'])
|
|
761
|
+
.rightJoin('orders', 'id', 'userId');
|
|
762
|
+
// rightResult 类型: { users: Partial<...>, orders: {...} }[]
|
|
763
|
+
|
|
764
|
+
// 多表 JOIN - 链式调用
|
|
765
|
+
const multiJoin = await db.select('orders', ['id', 'amount'])
|
|
766
|
+
.join('users', 'userId', 'id')
|
|
767
|
+
.leftJoin('products', 'productId', 'id')
|
|
768
|
+
.where({ amount: { $gt: 100 } });
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
**JOIN 方法:**
|
|
772
|
+
|
|
773
|
+
| 方法 | SQL | 返回类型 |
|
|
774
|
+
|------|-----|----------|
|
|
775
|
+
| `.join(table, left, right)` | `INNER JOIN` | `{ 主表: {...}, 关联表: {...} }` |
|
|
776
|
+
| `.leftJoin(table, left, right)` | `LEFT JOIN` | `{ 主表: {...}, 关联表: {...} \| null }` |
|
|
777
|
+
| `.rightJoin(table, left, right)` | `RIGHT JOIN` | `{ 主表: Partial<...>, 关联表: {...} }` |
|
|
778
|
+
|
|
779
|
+
### 软删除
|
|
82
780
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
781
|
+
启用软删除后,`delete()` 不会物理删除数据,而是设置 `deletedAt` 字段:
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
import { RelatedModel } from '@zhin.js/database';
|
|
785
|
+
|
|
786
|
+
// 创建带软删除的模型
|
|
787
|
+
const userModel = new RelatedModel(db, 'users', {
|
|
788
|
+
softDelete: true,
|
|
789
|
+
deletedAtField: 'deletedAt' // 可选,默认 'deletedAt'
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
// 删除 → 实际执行: UPDATE users SET deletedAt = NOW() WHERE id = 1
|
|
793
|
+
await userModel.delete({ id: 1 });
|
|
794
|
+
|
|
795
|
+
// 普通查询 → 自动排除已删除: SELECT * FROM users WHERE deletedAt IS NULL
|
|
796
|
+
const activeUsers = await userModel.select('id', 'name');
|
|
797
|
+
|
|
798
|
+
// 查询包含已删除的记录
|
|
799
|
+
const allUsers = await userModel.selectWithTrashed('id', 'name');
|
|
800
|
+
|
|
801
|
+
// 仅查询已删除的记录
|
|
802
|
+
const deletedUsers = await userModel.selectOnlyTrashed('id', 'name');
|
|
803
|
+
|
|
804
|
+
// 恢复已删除的记录 → UPDATE users SET deletedAt = NULL WHERE id = 1
|
|
805
|
+
await userModel.restore({ id: 1 });
|
|
806
|
+
|
|
807
|
+
// 强制物理删除(忽略软删除设置)
|
|
808
|
+
await userModel.forceDelete({ id: 1 });
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
**软删除方法:**
|
|
812
|
+
|
|
813
|
+
| 方法 | 说明 |
|
|
814
|
+
|------|------|
|
|
815
|
+
| `model.delete(condition)` | 软删除(设置 deletedAt) |
|
|
816
|
+
| `model.select(...)` | 自动排除已删除 |
|
|
817
|
+
| `model.selectWithTrashed(...)` | 包含已删除 |
|
|
818
|
+
| `model.selectOnlyTrashed(...)` | 仅已删除 |
|
|
819
|
+
| `model.restore(condition)` | 恢复软删除 |
|
|
820
|
+
| `model.forceDelete(condition)` | 物理删除 |
|
|
821
|
+
|
|
822
|
+
### 自动时间戳
|
|
823
|
+
|
|
824
|
+
启用后自动管理 `createdAt` 和 `updatedAt` 字段:
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
const userModel = new RelatedModel(db, 'users', {
|
|
828
|
+
timestamps: true,
|
|
829
|
+
createdAtField: 'createdAt', // 可选
|
|
830
|
+
updatedAtField: 'updatedAt' // 可选
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// 插入时自动设置 createdAt 和 updatedAt
|
|
834
|
+
await userModel.insert({ name: 'John' });
|
|
835
|
+
// INSERT INTO users (name, createdAt, updatedAt) VALUES ('John', NOW(), NOW())
|
|
836
|
+
|
|
837
|
+
// 更新时自动更新 updatedAt
|
|
838
|
+
await userModel.update({ name: 'Jane' }).where({ id: 1 });
|
|
839
|
+
// UPDATE users SET name = 'Jane', updatedAt = NOW() WHERE id = 1
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### 子查询
|
|
843
|
+
|
|
844
|
+
支持在 `$in` 和 `$nin` 操作符中使用子查询,**带完整类型推断**:
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
interface Schema {
|
|
848
|
+
users: { id: number; name: string; status: string };
|
|
849
|
+
orders: { id: number; userId: number; amount: number };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// 查询购买过高价商品的用户
|
|
853
|
+
const users = await db.select('users', ['id', 'name'])
|
|
854
|
+
.where({
|
|
855
|
+
id: {
|
|
856
|
+
$in: db.select('orders', ['userId']).where({ amount: { $gt: 1000 } })
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
// SQL: SELECT id, name FROM users WHERE id IN (SELECT userId FROM orders WHERE amount > 1000)
|
|
860
|
+
|
|
861
|
+
// 查询没有下过订单的用户
|
|
862
|
+
const inactiveUsers = await db.select('users', ['id', 'name'])
|
|
863
|
+
.where({
|
|
864
|
+
id: {
|
|
865
|
+
$nin: db.select('orders', ['userId'])
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
// SQL: SELECT id, name FROM users WHERE id NOT IN (SELECT userId FROM orders)
|
|
869
|
+
|
|
870
|
+
// ✅ 类型安全:子查询返回类型必须与字段类型匹配
|
|
871
|
+
db.select('users', ['id']).where({
|
|
872
|
+
id: { $in: db.select('orders', ['userId']) } // ✅ number 匹配 number
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
db.select('users', ['id']).where({
|
|
876
|
+
id: { $in: db.select('users', ['name']) } // ❌ 类型错误!string 不能匹配 number
|
|
877
|
+
});
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### 查询日志
|
|
881
|
+
|
|
882
|
+
启用查询日志,方便调试和性能分析:
|
|
883
|
+
|
|
884
|
+
```typescript
|
|
885
|
+
// 启用默认日志(输出到控制台)
|
|
886
|
+
db.enableLogging();
|
|
887
|
+
|
|
888
|
+
// 执行查询时自动输出日志
|
|
889
|
+
await db.select('users', ['id', 'name']).where({ status: 'active' });
|
|
890
|
+
// [SQL] SELECT id, name FROM users WHERE status = ? ["active"] → ✅ 3ms
|
|
891
|
+
|
|
892
|
+
await db.insert('logs', { message: 'test' });
|
|
893
|
+
// [SQL] INSERT INTO logs (message) VALUES (?) ["test"] → ✅ 1ms
|
|
894
|
+
|
|
895
|
+
// 错误时也会记录
|
|
896
|
+
await db.query('SELECT * FROM not_exist');
|
|
897
|
+
// [SQL] SELECT * FROM not_exist → ❌ ERROR: no such table: not_exist
|
|
898
|
+
|
|
899
|
+
// 自定义日志处理器
|
|
900
|
+
db.enableLogging(({ sql, params, duration, error }) => {
|
|
901
|
+
if (error) {
|
|
902
|
+
logger.error(`Query failed: ${sql}`, { params, error });
|
|
903
|
+
} else if (duration > 100) {
|
|
904
|
+
logger.warn(`Slow query: ${sql}`, { params, duration });
|
|
905
|
+
} else {
|
|
906
|
+
logger.debug(`Query: ${sql}`, { params, duration });
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// 禁用日志
|
|
911
|
+
db.disableLogging();
|
|
912
|
+
|
|
913
|
+
// 检查日志状态
|
|
914
|
+
if (db.isLogging) {
|
|
915
|
+
console.log('Query logging is enabled');
|
|
916
|
+
}
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### 连接池
|
|
920
|
+
|
|
921
|
+
MySQL 和 PostgreSQL 支持连接池,提高高并发场景下的性能:
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
// MySQL 连接池配置
|
|
925
|
+
const db = Registry.create('mysql', {
|
|
926
|
+
host: 'localhost',
|
|
927
|
+
port: 3306,
|
|
928
|
+
user: 'root',
|
|
929
|
+
password: 'password',
|
|
930
|
+
database: 'myapp',
|
|
931
|
+
pool: {
|
|
932
|
+
min: 2, // 最小连接数
|
|
933
|
+
max: 10, // 最大连接数
|
|
934
|
+
idleTimeoutMillis: 30000, // 空闲连接超时(毫秒)
|
|
935
|
+
acquireTimeoutMillis: 10000 // 获取连接超时(毫秒)
|
|
936
|
+
}
|
|
937
|
+
}, schemas);
|
|
938
|
+
|
|
939
|
+
// PostgreSQL 连接池配置
|
|
940
|
+
const db = Registry.create('pg', {
|
|
941
|
+
host: 'localhost',
|
|
942
|
+
port: 5432,
|
|
943
|
+
user: 'postgres',
|
|
944
|
+
password: 'password',
|
|
945
|
+
database: 'myapp',
|
|
946
|
+
pool: {
|
|
947
|
+
min: 2,
|
|
948
|
+
max: 10,
|
|
949
|
+
idleTimeoutMillis: 30000,
|
|
950
|
+
acquireTimeoutMillis: 10000
|
|
951
|
+
}
|
|
952
|
+
}, schemas);
|
|
953
|
+
|
|
954
|
+
await db.start();
|
|
955
|
+
|
|
956
|
+
// 连接池模式下的事务会自动获取专用连接
|
|
957
|
+
await db.transaction(async (trx) => {
|
|
958
|
+
// 这个事务使用连接池中的一个专用连接
|
|
959
|
+
await trx.query('...');
|
|
960
|
+
});
|
|
961
|
+
// 事务结束后连接自动归还到池中
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
**连接池 vs 单连接:**
|
|
965
|
+
|
|
966
|
+
| 特性 | 单连接 | 连接池 |
|
|
967
|
+
|------|--------|--------|
|
|
968
|
+
| 适用场景 | 低并发、简单应用 | 高并发、生产环境 |
|
|
969
|
+
| 连接数 | 1 | 可配置 (min-max) |
|
|
970
|
+
| 事务隔离 | 自然隔离 | 自动获取专用连接 |
|
|
971
|
+
| 资源利用 | 简单 | 高效复用 |
|
|
972
|
+
|
|
973
|
+
## 关联关系 (Relations)
|
|
974
|
+
|
|
975
|
+
支持定义和查询模型之间的关联关系,解决 N+1 查询问题。
|
|
976
|
+
|
|
977
|
+
### 方式一:预定义关系配置(推荐)
|
|
978
|
+
|
|
979
|
+
类似 Sequelize,在数据库层面一次性定义所有关系:
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
const db = Registry.create<MySchema, 'sqlite'>('sqlite', { filename: ':memory:' });
|
|
983
|
+
|
|
984
|
+
// 预定义关系配置
|
|
985
|
+
db.defineRelations({
|
|
986
|
+
users: {
|
|
987
|
+
hasMany: { orders: 'userId', posts: 'authorId' },
|
|
988
|
+
hasOne: { profile: 'userId' }
|
|
989
|
+
},
|
|
990
|
+
orders: {
|
|
991
|
+
belongsTo: { users: 'userId' }
|
|
992
|
+
},
|
|
993
|
+
posts: {
|
|
994
|
+
belongsTo: { users: 'authorId' }
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
// 获取模型时自动应用关系,无需手动调用 hasMany/belongsTo
|
|
999
|
+
const userModel = db.model('users');
|
|
1000
|
+
const usersWithOrders = await userModel.with('orders', 'profile');
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
### 方式二:模型实例定义关系
|
|
1004
|
+
|
|
1005
|
+
```typescript
|
|
1006
|
+
const userModel = db.model('users');
|
|
1007
|
+
const orderModel = db.model('orders');
|
|
1008
|
+
const profileModel = db.model('profiles');
|
|
1009
|
+
|
|
1010
|
+
// 一对多: User hasMany Orders (orders.userId -> users.id)
|
|
1011
|
+
userModel.hasMany(orderModel, 'userId');
|
|
1012
|
+
|
|
1013
|
+
// 多对一: Order belongsTo User (orders.userId -> users.id)
|
|
1014
|
+
orderModel.belongsTo(userModel, 'userId');
|
|
1015
|
+
|
|
1016
|
+
// 一对一: User hasOne Profile (profiles.userId -> users.id)
|
|
1017
|
+
userModel.hasOne(profileModel, 'userId');
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
**类型安全**:传入模型实例而不是字符串,确保外键字段名正确:
|
|
1021
|
+
```typescript
|
|
1022
|
+
// ✅ 类型检查通过
|
|
1023
|
+
orderModel.belongsTo(userModel, 'userId');
|
|
1024
|
+
|
|
1025
|
+
// ❌ 类型错误:'wrongKey' 不存在于 orders 表
|
|
1026
|
+
orderModel.belongsTo(userModel, 'wrongKey');
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
### 加载单条记录的关联
|
|
1030
|
+
|
|
1031
|
+
```typescript
|
|
1032
|
+
const user = await userModel.selectById(1);
|
|
1033
|
+
const userWithOrders = await userModel.loadRelation(user, 'orders');
|
|
1034
|
+
|
|
1035
|
+
console.log(userWithOrders.orders);
|
|
1036
|
+
// [{ id: 1, productName: 'A' }, { id: 2, productName: 'B' }]
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
### 批量预加载(解决 N+1)
|
|
1040
|
+
|
|
1041
|
+
```typescript
|
|
1042
|
+
const users = await userModel.select();
|
|
1043
|
+
const usersWithOrders = await userModel.loadRelations(users, ['orders']);
|
|
1044
|
+
|
|
1045
|
+
// 只执行 2 次查询:
|
|
1046
|
+
// 1. SELECT * FROM users
|
|
1047
|
+
// 2. SELECT * FROM orders WHERE userId IN (1, 2, 3...)
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
### 链式调用 `.with()`
|
|
1051
|
+
|
|
1052
|
+
```typescript
|
|
1053
|
+
const usersWithOrders = await userModel.with('orders')
|
|
1054
|
+
.where({ status: 'active' })
|
|
1055
|
+
.orderBy('id', 'ASC')
|
|
1056
|
+
.limit(10);
|
|
1057
|
+
|
|
1058
|
+
// 每个 user 都自动带有 orders 数组
|
|
1059
|
+
usersWithOrders.forEach(user => {
|
|
1060
|
+
console.log(`${user.name} has ${user.orders.length} orders`);
|
|
1061
|
+
});
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
### 关系类型
|
|
1065
|
+
|
|
1066
|
+
| 方法 | 描述 | 返回值 |
|
|
1067
|
+
|------|------|--------|
|
|
1068
|
+
| `hasMany(targetModel, foreignKey)` | 一对多 | `T[]` |
|
|
1069
|
+
| `belongsTo(targetModel, foreignKey)` | 多对一 | `T \| null` |
|
|
1070
|
+
| `hasOne(targetModel, foreignKey)` | 一对一 | `T \| null` |
|
|
1071
|
+
| `belongsToMany(targetModel, pivot, fk, rk)` | 多对多 | `T[]` |
|
|
1072
|
+
|
|
1073
|
+
### 多对多关系 (belongsToMany)
|
|
1074
|
+
|
|
1075
|
+
多对多关系需要一个中间表(pivot table)来存储两个表之间的关联。
|
|
1076
|
+
|
|
1077
|
+
#### 基本用法
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
const userModel = db.model('users');
|
|
1081
|
+
const roleModel = db.model('roles');
|
|
1082
|
+
|
|
1083
|
+
// User belongsToMany Roles (通过 user_roles 中间表)
|
|
1084
|
+
// user_roles 表结构: { user_id, role_id }
|
|
1085
|
+
userModel.belongsToMany(
|
|
1086
|
+
roleModel, // 目标模型
|
|
1087
|
+
'user_roles', // 中间表名
|
|
1088
|
+
'user_id', // 中间表中指向本表的外键
|
|
1089
|
+
'role_id' // 中间表中指向目标表的外键
|
|
1090
|
+
);
|
|
1091
|
+
|
|
1092
|
+
// 双向关系
|
|
1093
|
+
roleModel.belongsToMany(userModel, 'user_roles', 'role_id', 'user_id');
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
#### 加载关联数据
|
|
1097
|
+
|
|
1098
|
+
```typescript
|
|
1099
|
+
// 单条记录加载
|
|
1100
|
+
const user = await userModel.findById(1);
|
|
1101
|
+
const userWithRoles = await userModel.loadRelation(user, 'roles');
|
|
1102
|
+
console.log(userWithRoles.roles); // [{ id: 1, name: 'admin' }, { id: 2, name: 'editor' }]
|
|
1103
|
+
|
|
1104
|
+
// 批量加载(with)
|
|
1105
|
+
const usersWithRoles = await userModel.with('roles');
|
|
1106
|
+
usersWithRoles.forEach(user => {
|
|
1107
|
+
console.log(`${user.name} has roles: ${user.roles.map(r => r.name).join(', ')}`);
|
|
1108
|
+
});
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
#### 访问中间表数据
|
|
1112
|
+
|
|
1113
|
+
如果中间表有额外字段(如 `created_at`、`sort_order` 等),可以通过 `pivotFields` 参数获取:
|
|
1114
|
+
|
|
1115
|
+
```typescript
|
|
1116
|
+
// 中间表: post_tags { post_id, tag_id, sort_order }
|
|
1117
|
+
postModel.belongsToMany(
|
|
1118
|
+
tagModel,
|
|
1119
|
+
'post_tags',
|
|
1120
|
+
'post_id',
|
|
1121
|
+
'tag_id',
|
|
1122
|
+
'id', // 本表主键
|
|
1123
|
+
'id', // 目标表主键
|
|
1124
|
+
['sort_order'] // 需要获取的中间表字段
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
const postWithTags = await postModel.loadRelation(post, 'tags');
|
|
1128
|
+
postWithTags.tags.forEach(tag => {
|
|
1129
|
+
console.log(`${tag.name} - sort: ${tag.pivot.sort_order}`);
|
|
1130
|
+
});
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
#### Schema 预定义多对多关系
|
|
1134
|
+
|
|
1135
|
+
```typescript
|
|
1136
|
+
db.defineRelations({
|
|
1137
|
+
users: {
|
|
1138
|
+
belongsToMany: {
|
|
1139
|
+
roles: {
|
|
1140
|
+
pivot: 'user_roles',
|
|
1141
|
+
foreignKey: 'user_id',
|
|
1142
|
+
relatedKey: 'role_id',
|
|
1143
|
+
pivotFields: ['assigned_at'] // 可选
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
roles: {
|
|
1148
|
+
belongsToMany: {
|
|
1149
|
+
users: {
|
|
1150
|
+
pivot: 'user_roles',
|
|
1151
|
+
foreignKey: 'role_id',
|
|
1152
|
+
relatedKey: 'user_id'
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
## 生命周期钩子 (Lifecycle Hooks)
|
|
1160
|
+
|
|
1161
|
+
在 CRUD 操作的关键节点执行自定义逻辑。
|
|
1162
|
+
|
|
1163
|
+
### 支持的钩子
|
|
1164
|
+
|
|
1165
|
+
| 钩子名称 | 触发时机 | 可取消操作 |
|
|
1166
|
+
|---------|---------|-----------|
|
|
1167
|
+
| `beforeCreate` | 创建前 | ✅ 返回 `false` |
|
|
1168
|
+
| `afterCreate` | 创建后 | - |
|
|
1169
|
+
| `beforeFind` | 查询前 | ✅ 返回 `false` |
|
|
1170
|
+
| `afterFind` | 查询后 | - |
|
|
1171
|
+
| `beforeUpdate` | 更新前 | ✅ 返回 `false` |
|
|
1172
|
+
| `afterUpdate` | 更新后 | - |
|
|
1173
|
+
| `beforeDelete` | 删除前 | ✅ 返回 `false` |
|
|
1174
|
+
| `afterDelete` | 删除后 | - |
|
|
1175
|
+
|
|
1176
|
+
### 注册钩子
|
|
1177
|
+
|
|
1178
|
+
```typescript
|
|
1179
|
+
const userModel = db.model('users');
|
|
1180
|
+
|
|
1181
|
+
// 方式一:addHook(链式调用)
|
|
1182
|
+
userModel
|
|
1183
|
+
.addHook('beforeCreate', (ctx) => {
|
|
1184
|
+
// 自动生成 slug
|
|
1185
|
+
ctx.data.slug = slugify(ctx.data.name);
|
|
1186
|
+
})
|
|
1187
|
+
.addHook('afterCreate', async (ctx) => {
|
|
1188
|
+
// 记录日志
|
|
1189
|
+
await logService.log('User created', ctx.result);
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
// 方式二:on(别名)
|
|
1193
|
+
userModel.on('beforeDelete', (ctx) => {
|
|
1194
|
+
console.log('About to delete:', ctx.where);
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
// 方式三:批量注册
|
|
1198
|
+
userModel.registerHooks({
|
|
1199
|
+
beforeCreate: (ctx) => { /* ... */ },
|
|
1200
|
+
afterUpdate: [
|
|
1201
|
+
(ctx) => { /* hook 1 */ },
|
|
1202
|
+
(ctx) => { /* hook 2 */ }
|
|
1203
|
+
]
|
|
1204
|
+
});
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
### 钩子上下文
|
|
1208
|
+
|
|
1209
|
+
```typescript
|
|
1210
|
+
interface HookContext<T> {
|
|
1211
|
+
modelName: string; // 模型名称
|
|
1212
|
+
data?: Partial<T>; // 创建/更新的数据
|
|
1213
|
+
where?: Condition<T>; // 查询/更新/删除条件
|
|
1214
|
+
result?: T | T[] | number; // 操作结果(after 钩子)
|
|
1215
|
+
}
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
### 取消操作
|
|
1219
|
+
|
|
1220
|
+
`before` 钩子返回 `false` 可以取消操作:
|
|
1221
|
+
|
|
1222
|
+
```typescript
|
|
1223
|
+
userModel.addHook('beforeDelete', async (ctx) => {
|
|
1224
|
+
// 禁止删除管理员
|
|
1225
|
+
const user = await userModel.findOne(ctx.where);
|
|
1226
|
+
if (user?.role === 'admin') {
|
|
1227
|
+
return false; // 取消删除
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
await userModel.deleteById(1); // 如果是管理员,返回 false
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
### 修改数据
|
|
1235
|
+
|
|
1236
|
+
`beforeCreate` 和 `beforeUpdate` 可以修改数据:
|
|
1237
|
+
|
|
1238
|
+
```typescript
|
|
1239
|
+
userModel.addHook('beforeCreate', (ctx) => {
|
|
1240
|
+
// 统一处理
|
|
1241
|
+
ctx.data.status = 'pending';
|
|
1242
|
+
ctx.data.name = ctx.data.name?.trim().toLowerCase();
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
userModel.addHook('beforeUpdate', (ctx) => {
|
|
1246
|
+
// 自动更新时间
|
|
1247
|
+
ctx.data.updatedAt = new Date();
|
|
1248
|
+
});
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
### 转换结果
|
|
1252
|
+
|
|
1253
|
+
`afterFind` 可以转换查询结果:
|
|
1254
|
+
|
|
1255
|
+
```typescript
|
|
1256
|
+
userModel.addHook('afterFind', (ctx) => {
|
|
1257
|
+
if (ctx.result && !Array.isArray(ctx.result)) {
|
|
1258
|
+
// 添加计算属性
|
|
1259
|
+
ctx.result.fullName = `${ctx.result.firstName} ${ctx.result.lastName}`;
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
### 移除钩子
|
|
1265
|
+
|
|
1266
|
+
```typescript
|
|
1267
|
+
const myHook = (ctx) => { /* ... */ };
|
|
1268
|
+
userModel.addHook('beforeCreate', myHook);
|
|
1269
|
+
|
|
1270
|
+
// 移除特定钩子
|
|
1271
|
+
userModel.removeHook('beforeCreate', myHook);
|
|
1272
|
+
|
|
1273
|
+
// 移除某类型的所有钩子
|
|
1274
|
+
userModel.removeHook('beforeCreate');
|
|
1275
|
+
|
|
1276
|
+
// 清除所有钩子
|
|
1277
|
+
userModel.clearHooks();
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
### 使用钩子的 CRUD 方法
|
|
1281
|
+
|
|
1282
|
+
| 方法 | 触发的钩子 |
|
|
1283
|
+
|------|-----------|
|
|
1284
|
+
| `create(data)` | beforeCreate → afterCreate |
|
|
1285
|
+
| `findOne(where)` / `findById(id)` | beforeFind → afterFind |
|
|
1286
|
+
| `findAll(where)` | beforeFind → afterFind |
|
|
1287
|
+
| `updateWhere(where, data)` / `updateById(id, data)` | beforeUpdate → afterUpdate |
|
|
1288
|
+
| `deleteWhere(where)` / `deleteById(id)` | beforeDelete → afterDelete |
|
|
1289
|
+
|
|
1290
|
+
> 注意:直接使用 `insert()`, `select()`, `update()`, `delete()` 链式查询 **不会** 触发钩子,需要使用上述便捷方法。
|
|
1291
|
+
|
|
1292
|
+
## 数据迁移 (Migration)
|
|
1293
|
+
|
|
1294
|
+
版本化的数据库结构变更管理,支持升级和回滚。**`down` 操作可自动生成,无需手动编写。**
|
|
1295
|
+
|
|
1296
|
+
### 基本用法
|
|
1297
|
+
|
|
1298
|
+
```typescript
|
|
1299
|
+
import { MigrationRunner, defineMigration } from '@zhin.js/database';
|
|
1300
|
+
|
|
1301
|
+
const runner = new MigrationRunner(db);
|
|
1302
|
+
|
|
1303
|
+
// 定义迁移 - 只需写 up,down 会自动生成!
|
|
1304
|
+
runner.add(defineMigration({
|
|
1305
|
+
name: '001_create_users',
|
|
1306
|
+
up: async (ctx) => {
|
|
1307
|
+
await ctx.createTable('users', {
|
|
1308
|
+
id: { type: 'integer', primary: true, autoIncrement: true },
|
|
1309
|
+
name: { type: 'text', nullable: false },
|
|
1310
|
+
email: { type: 'text', unique: true },
|
|
1311
|
+
createdAt: { type: 'date' }
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
// down 自动生成: ctx.dropTable('users')
|
|
1315
|
+
}));
|
|
1316
|
+
|
|
1317
|
+
runner.add(defineMigration({
|
|
1318
|
+
name: '002_add_user_status',
|
|
1319
|
+
up: async (ctx) => {
|
|
1320
|
+
await ctx.addColumn('users', 'status', { type: 'text', default: 'active' });
|
|
1321
|
+
await ctx.addIndex('users', 'idx_status', ['status']);
|
|
1322
|
+
}
|
|
1323
|
+
// down 自动生成:
|
|
1324
|
+
// ctx.dropIndex('users', 'idx_status')
|
|
1325
|
+
// ctx.dropColumn('users', 'status')
|
|
1326
|
+
}));
|
|
1327
|
+
|
|
1328
|
+
// 运行所有待执行的迁移
|
|
1329
|
+
await runner.migrate();
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
### 自动生成 Down 的规则
|
|
1333
|
+
|
|
1334
|
+
系统会记录 `up` 中执行的操作,并自动生成反向操作:
|
|
1335
|
+
|
|
1336
|
+
| up 操作 | 自动生成的 down 操作 |
|
|
1337
|
+
|---------|---------------------|
|
|
1338
|
+
| `createTable(name, columns)` | `dropTable(name)` |
|
|
1339
|
+
| `addColumn(table, col, def)` | `dropColumn(table, col)` |
|
|
1340
|
+
| `addIndex(table, idx, cols)` | `dropIndex(table, idx)` |
|
|
1341
|
+
| `renameColumn(table, old, new)` | `renameColumn(table, new, old)` |
|
|
1342
|
+
|
|
1343
|
+
以下操作**无法自动生成** down(需要手动提供):
|
|
1344
|
+
- `dropTable` - 需要原始表结构才能恢复
|
|
1345
|
+
- `dropColumn` - 需要原始列定义才能恢复
|
|
1346
|
+
- `dropIndex` - 需要原始索引定义才能恢复
|
|
1347
|
+
- `query` - 原生 SQL 无法自动反向
|
|
1348
|
+
|
|
1349
|
+
### 手动提供 down
|
|
1350
|
+
|
|
1351
|
+
对于复杂场景或无法自动反向的操作,可以显式提供 `down`:
|
|
1352
|
+
|
|
1353
|
+
```typescript
|
|
1354
|
+
runner.add(defineMigration({
|
|
1355
|
+
name: '003_seed_settings',
|
|
1356
|
+
up: async (ctx) => {
|
|
1357
|
+
// 原生 SQL 无法自动反向
|
|
1358
|
+
await ctx.query(`INSERT INTO settings VALUES (?, ?)`, ['theme', 'dark']);
|
|
1359
|
+
},
|
|
1360
|
+
down: async (ctx) => {
|
|
1361
|
+
await ctx.query(`DELETE FROM settings WHERE key = ?`, ['theme']);
|
|
1362
|
+
}
|
|
1363
|
+
}));
|
|
1364
|
+
```
|
|
1365
|
+
|
|
1366
|
+
### 迁移操作
|
|
1367
|
+
|
|
1368
|
+
```typescript
|
|
1369
|
+
// 运行所有待执行的迁移
|
|
1370
|
+
const migrated = await runner.migrate();
|
|
1371
|
+
console.log('已迁移:', migrated);
|
|
1372
|
+
|
|
1373
|
+
// 回滚最后一批迁移
|
|
1374
|
+
const rolledBack = await runner.rollback();
|
|
1375
|
+
console.log('已回滚:', rolledBack);
|
|
1376
|
+
|
|
1377
|
+
// 回滚所有迁移
|
|
1378
|
+
await runner.reset();
|
|
1379
|
+
|
|
1380
|
+
// 重新运行所有迁移 (reset + migrate)
|
|
1381
|
+
const { rolledBack, migrated } = await runner.refresh();
|
|
1382
|
+
|
|
1383
|
+
// 查看迁移状态
|
|
1384
|
+
const status = await runner.status();
|
|
1385
|
+
// [
|
|
1386
|
+
// { name: '001_create_users', status: 'executed', batch: 1 },
|
|
1387
|
+
// { name: '002_add_status', status: 'pending' }
|
|
1388
|
+
// ]
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
### 迁移上下文 API
|
|
1392
|
+
|
|
1393
|
+
| 方法 | 描述 |
|
|
1394
|
+
|------|------|
|
|
1395
|
+
| `createTable(name, columns)` | 创建表 |
|
|
1396
|
+
| `dropTable(name)` | 删除表 |
|
|
1397
|
+
| `addColumn(table, column, definition)` | 添加列 |
|
|
1398
|
+
| `dropColumn(table, column)` | 删除列 |
|
|
1399
|
+
| `renameColumn(table, oldName, newName)` | 重命名列 |
|
|
1400
|
+
| `addIndex(table, indexName, columns, unique?)` | 添加索引 |
|
|
1401
|
+
| `dropIndex(table, indexName)` | 删除索引 |
|
|
1402
|
+
| `query(sql, params?)` | 执行原生 SQL |
|
|
1403
|
+
|
|
1404
|
+
### 批量添加迁移
|
|
1405
|
+
|
|
1406
|
+
```typescript
|
|
1407
|
+
runner.addAll([
|
|
1408
|
+
defineMigration({ name: '001_users', up: async (ctx) => { /* ... */ } }),
|
|
1409
|
+
defineMigration({ name: '002_orders', up: async (ctx) => { /* ... */ } }),
|
|
1410
|
+
defineMigration({ name: '003_products', up: async (ctx) => { /* ... */ } }),
|
|
1411
|
+
]);
|
|
1412
|
+
|
|
1413
|
+
// 同一批次的迁移会一起执行/回滚
|
|
1414
|
+
await runner.migrate();
|
|
1415
|
+
```
|
|
90
1416
|
|
|
91
1417
|
## License
|
|
92
1418
|
|