@zhin.js/database 1.0.4 → 1.0.6

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1360 -34
  3. package/lib/base/database.d.ts +71 -13
  4. package/lib/base/database.d.ts.map +1 -1
  5. package/lib/base/database.js +128 -4
  6. package/lib/base/database.js.map +1 -1
  7. package/lib/base/dialect.d.ts +27 -10
  8. package/lib/base/dialect.d.ts.map +1 -1
  9. package/lib/base/dialect.js +32 -0
  10. package/lib/base/dialect.js.map +1 -1
  11. package/lib/base/index.d.ts +1 -0
  12. package/lib/base/index.d.ts.map +1 -1
  13. package/lib/base/index.js +1 -0
  14. package/lib/base/index.js.map +1 -1
  15. package/lib/base/model.d.ts +105 -12
  16. package/lib/base/model.d.ts.map +1 -1
  17. package/lib/base/model.js +224 -3
  18. package/lib/base/model.js.map +1 -1
  19. package/lib/base/query-classes.d.ts +204 -33
  20. package/lib/base/query-classes.d.ts.map +1 -1
  21. package/lib/base/query-classes.js +276 -0
  22. package/lib/base/query-classes.js.map +1 -1
  23. package/lib/base/thenable.d.ts +7 -7
  24. package/lib/base/thenable.d.ts.map +1 -1
  25. package/lib/base/thenable.js +5 -4
  26. package/lib/base/thenable.js.map +1 -1
  27. package/lib/base/transaction.d.ts +46 -0
  28. package/lib/base/transaction.d.ts.map +1 -0
  29. package/lib/base/transaction.js +186 -0
  30. package/lib/base/transaction.js.map +1 -0
  31. package/lib/dialects/memory.d.ts +12 -7
  32. package/lib/dialects/memory.d.ts.map +1 -1
  33. package/lib/dialects/memory.js +7 -4
  34. package/lib/dialects/memory.js.map +1 -1
  35. package/lib/dialects/mongodb.d.ts +11 -7
  36. package/lib/dialects/mongodb.d.ts.map +1 -1
  37. package/lib/dialects/mongodb.js +18 -15
  38. package/lib/dialects/mongodb.js.map +1 -1
  39. package/lib/dialects/mysql.d.ts +35 -6
  40. package/lib/dialects/mysql.d.ts.map +1 -1
  41. package/lib/dialects/mysql.js +137 -18
  42. package/lib/dialects/mysql.js.map +1 -1
  43. package/lib/dialects/pg.d.ts +35 -6
  44. package/lib/dialects/pg.d.ts.map +1 -1
  45. package/lib/dialects/pg.js +137 -18
  46. package/lib/dialects/pg.js.map +1 -1
  47. package/lib/dialects/redis.d.ts +11 -6
  48. package/lib/dialects/redis.d.ts.map +1 -1
  49. package/lib/dialects/redis.js +11 -8
  50. package/lib/dialects/redis.js.map +1 -1
  51. package/lib/dialects/sqlite.d.ts +19 -6
  52. package/lib/dialects/sqlite.d.ts.map +1 -1
  53. package/lib/dialects/sqlite.js +63 -10
  54. package/lib/dialects/sqlite.js.map +1 -1
  55. package/lib/index.d.ts +1 -0
  56. package/lib/index.d.ts.map +1 -1
  57. package/lib/index.js +1 -0
  58. package/lib/index.js.map +1 -1
  59. package/lib/migration.d.ts +132 -0
  60. package/lib/migration.d.ts.map +1 -0
  61. package/lib/migration.js +475 -0
  62. package/lib/migration.js.map +1 -0
  63. package/lib/registry.d.ts +26 -23
  64. package/lib/registry.d.ts.map +1 -1
  65. package/lib/registry.js +1 -5
  66. package/lib/registry.js.map +1 -1
  67. package/lib/type/document/database.d.ts +11 -11
  68. package/lib/type/document/database.d.ts.map +1 -1
  69. package/lib/type/document/database.js.map +1 -1
  70. package/lib/type/document/model.d.ts +7 -7
  71. package/lib/type/document/model.d.ts.map +1 -1
  72. package/lib/type/document/model.js.map +1 -1
  73. package/lib/type/keyvalue/database.d.ts +11 -11
  74. package/lib/type/keyvalue/database.d.ts.map +1 -1
  75. package/lib/type/keyvalue/database.js.map +1 -1
  76. package/lib/type/keyvalue/model.d.ts +2 -2
  77. package/lib/type/keyvalue/model.d.ts.map +1 -1
  78. package/lib/type/keyvalue/model.js.map +1 -1
  79. package/lib/type/related/database.d.ts +48 -13
  80. package/lib/type/related/database.d.ts.map +1 -1
  81. package/lib/type/related/database.js +258 -27
  82. package/lib/type/related/database.js.map +1 -1
  83. package/lib/type/related/model.d.ts +251 -15
  84. package/lib/type/related/model.d.ts.map +1 -1
  85. package/lib/type/related/model.js +647 -22
  86. package/lib/type/related/model.js.map +1 -1
  87. package/lib/types.d.ts +475 -37
  88. package/lib/types.d.ts.map +1 -1
  89. package/lib/types.js +6 -0
  90. package/lib/types.js.map +1 -1
  91. package/package.json +14 -5
  92. package/src/base/database.ts +168 -24
  93. package/src/base/dialect.ts +49 -10
  94. package/src/base/index.ts +2 -1
  95. package/src/base/model.ts +258 -18
  96. package/src/base/query-classes.ts +471 -63
  97. package/src/base/thenable.ts +12 -11
  98. package/src/base/transaction.ts +213 -0
  99. package/src/dialects/memory.ts +14 -13
  100. package/src/dialects/mongodb.ts +40 -38
  101. package/src/dialects/mysql.ts +151 -22
  102. package/src/dialects/pg.ts +148 -21
  103. package/src/dialects/redis.ts +40 -38
  104. package/src/dialects/sqlite.ts +73 -15
  105. package/src/index.ts +1 -2
  106. package/src/migration.ts +544 -0
  107. package/src/registry.ts +33 -33
  108. package/src/type/document/database.ts +32 -32
  109. package/src/type/document/model.ts +14 -14
  110. package/src/type/keyvalue/database.ts +32 -32
  111. package/src/type/keyvalue/model.ts +18 -18
  112. package/src/type/related/database.ts +309 -34
  113. package/src/type/related/model.ts +800 -33
  114. package/src/types.ts +559 -44
  115. 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 driver for zhin framework.
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 { Database } from '@zhin.js/database';
15
- import '@zhin.js/driver-sqlite'; // 导入方言
33
+ import { Registry } from '@zhin.js/database';
16
34
 
17
- // 创建数据库
18
- const db = Database.create('sqlite', {
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**: `@zhin.js/driver-sqlite`
42
- - **MySQL**: `@zhin.js/driver-mysql`
43
- - **PostgreSQL**: `@zhin.js/driver-postgresql`
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
- ### NoSQL Databases
46
- - **MongoDB**: `@zhin.js/driver-mongodb`
47
- - **Redis**: `@zhin.js/driver-redis`
93
+ ### SQLite Example
48
94
 
49
- ### In-Memory Database
50
- - **Memory**: Built-in (for testing)
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
- For relational databases (SQLite, MySQL, PostgreSQL)
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
- import { RelatedDatabase } from '@zhin.js/database';
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
- const db = new RelatedDatabase(dialect, schemas);
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
- ### DocumentDatabase
64
- For document databases (MongoDB)
237
+ ## 链式查询 API (Fluent Query Builder)
238
+
239
+ 链式查询提供了一种流畅、类型安全的方式来构建数据库查询。所有查询都是 **Thenable** 的,可以直接使用 `await` 或 `.then()` 执行。
240
+
241
+ ### 基本用法
65
242
 
66
243
  ```typescript
67
- import { DocumentDatabase } from '@zhin.js/database';
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
- const db = new DocumentDatabase(dialect, schemas);
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
- ### KeyValueDatabase
73
- For key-value stores (Redis)
263
+ ### Select 查询
74
264
 
75
265
  ```typescript
76
- import { KeyValueDatabase } from '@zhin.js/database';
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
- const db = new KeyValueDatabase(dialect, schemas);
285
+ // 链式方法说明
286
+ // .where(condition) - 添加查询条件
287
+ // .groupBy(...fields) - 分组字段
288
+ // .orderBy(field, dir) - 排序(ASC/DESC)
289
+ // .limit(count) - 限制返回数量
290
+ // .offset(count) - 跳过指定数量
79
291
  ```
80
292
 
81
- ## Features
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
- - **Type Safety**: Full TypeScript support
84
- - **Unified API**: Same interface for all database types
85
- - **Query Builder**: Fluent query building
86
- - **Schema Management**: Automatic table/collection creation
87
- - **Connection Management**: Automatic connection handling
88
- - **Transaction Support**: Built-in transaction support
89
- - **Migration Support**: Schema evolution support
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