@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.
Files changed (119) 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 +13 -8
  32. package/lib/dialects/memory.d.ts.map +1 -1
  33. package/lib/dialects/memory.js +10 -7
  34. package/lib/dialects/memory.js.map +1 -1
  35. package/lib/dialects/mongodb.d.ts +12 -8
  36. package/lib/dialects/mongodb.d.ts.map +1 -1
  37. package/lib/dialects/mongodb.js +21 -18
  38. package/lib/dialects/mongodb.js.map +1 -1
  39. package/lib/dialects/mysql.d.ts +36 -7
  40. package/lib/dialects/mysql.d.ts.map +1 -1
  41. package/lib/dialects/mysql.js +140 -21
  42. package/lib/dialects/mysql.js.map +1 -1
  43. package/lib/dialects/pg.d.ts +36 -7
  44. package/lib/dialects/pg.d.ts.map +1 -1
  45. package/lib/dialects/pg.js +140 -21
  46. package/lib/dialects/pg.js.map +1 -1
  47. package/lib/dialects/redis.d.ts +13 -8
  48. package/lib/dialects/redis.d.ts.map +1 -1
  49. package/lib/dialects/redis.js +14 -11
  50. package/lib/dialects/redis.js.map +1 -1
  51. package/lib/dialects/sqlite.d.ts +20 -7
  52. package/lib/dialects/sqlite.d.ts.map +1 -1
  53. package/lib/dialects/sqlite.js +66 -13
  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 +12 -12
  68. package/lib/type/document/database.d.ts.map +1 -1
  69. package/lib/type/document/database.js +1 -1
  70. package/lib/type/document/database.js.map +1 -1
  71. package/lib/type/document/model.d.ts +8 -8
  72. package/lib/type/document/model.d.ts.map +1 -1
  73. package/lib/type/document/model.js +1 -1
  74. package/lib/type/document/model.js.map +1 -1
  75. package/lib/type/keyvalue/database.d.ts +12 -12
  76. package/lib/type/keyvalue/database.d.ts.map +1 -1
  77. package/lib/type/keyvalue/database.js +1 -1
  78. package/lib/type/keyvalue/database.js.map +1 -1
  79. package/lib/type/keyvalue/model.d.ts +3 -3
  80. package/lib/type/keyvalue/model.d.ts.map +1 -1
  81. package/lib/type/keyvalue/model.js +1 -1
  82. package/lib/type/keyvalue/model.js.map +1 -1
  83. package/lib/type/related/database.d.ts +49 -14
  84. package/lib/type/related/database.d.ts.map +1 -1
  85. package/lib/type/related/database.js +259 -28
  86. package/lib/type/related/database.js.map +1 -1
  87. package/lib/type/related/model.d.ts +252 -16
  88. package/lib/type/related/model.d.ts.map +1 -1
  89. package/lib/type/related/model.js +648 -23
  90. package/lib/type/related/model.js.map +1 -1
  91. package/lib/types.d.ts +475 -37
  92. package/lib/types.d.ts.map +1 -1
  93. package/lib/types.js +6 -0
  94. package/lib/types.js.map +1 -1
  95. package/package.json +10 -5
  96. package/src/base/database.ts +168 -24
  97. package/src/base/dialect.ts +49 -10
  98. package/src/base/index.ts +2 -1
  99. package/src/base/model.ts +258 -18
  100. package/src/base/query-classes.ts +471 -63
  101. package/src/base/thenable.ts +12 -11
  102. package/src/base/transaction.ts +213 -0
  103. package/src/dialects/memory.ts +17 -16
  104. package/src/dialects/mongodb.ts +44 -42
  105. package/src/dialects/mysql.ts +155 -26
  106. package/src/dialects/pg.ts +152 -25
  107. package/src/dialects/redis.ts +45 -43
  108. package/src/dialects/sqlite.ts +77 -19
  109. package/src/index.ts +1 -2
  110. package/src/migration.ts +544 -0
  111. package/src/registry.ts +33 -33
  112. package/src/type/document/database.ts +33 -33
  113. package/src/type/document/model.ts +15 -15
  114. package/src/type/keyvalue/database.ts +33 -33
  115. package/src/type/keyvalue/model.ts +19 -19
  116. package/src/type/related/database.ts +309 -34
  117. package/src/type/related/model.ts +801 -34
  118. package/src/types.ts +559 -44
  119. package/tests/database.test.ts +1738 -0
@@ -1,29 +1,516 @@
1
- import { Model } from '../../base';
1
+ import { Model } from '../../base/index.js';
2
+ /**
3
+ * 关联查询构建器
4
+ * 支持链式调用预加载关联数据
5
+ */
6
+ export class RelationQueryBuilder {
7
+ model;
8
+ relationNames;
9
+ conditions = {};
10
+ orderings = [];
11
+ limitCount;
12
+ offsetCount;
13
+ selectedFields;
14
+ constructor(model, relationNames) {
15
+ this.model = model;
16
+ this.relationNames = relationNames;
17
+ }
18
+ /**
19
+ * 选择字段
20
+ */
21
+ select(...fields) {
22
+ this.selectedFields = fields;
23
+ return this;
24
+ }
25
+ /**
26
+ * 添加查询条件
27
+ */
28
+ where(condition) {
29
+ this.conditions = { ...this.conditions, ...condition };
30
+ return this;
31
+ }
32
+ /**
33
+ * 排序
34
+ */
35
+ orderBy(field, direction = 'ASC') {
36
+ this.orderings.push({ field, direction });
37
+ return this;
38
+ }
39
+ /**
40
+ * 限制数量
41
+ */
42
+ limit(count) {
43
+ this.limitCount = count;
44
+ return this;
45
+ }
46
+ /**
47
+ * 偏移量
48
+ */
49
+ offset(count) {
50
+ this.offsetCount = count;
51
+ return this;
52
+ }
53
+ /**
54
+ * 执行查询并加载关联
55
+ */
56
+ async then(onfulfilled) {
57
+ // 构建主查询
58
+ let selection = this.model.select(...(this.selectedFields || []));
59
+ if (Object.keys(this.conditions).length > 0) {
60
+ selection = selection.where(this.conditions);
61
+ }
62
+ for (const { field, direction } of this.orderings) {
63
+ selection = selection.orderBy(field, direction);
64
+ }
65
+ if (this.limitCount !== undefined) {
66
+ selection = selection.limit(this.limitCount);
67
+ }
68
+ if (this.offsetCount !== undefined) {
69
+ selection = selection.offset(this.offsetCount);
70
+ }
71
+ // 执行主查询
72
+ const records = await selection;
73
+ // 加载关联
74
+ const result = await this.model.loadRelations(records, this.relationNames);
75
+ return onfulfilled ? onfulfilled(result) : result;
76
+ }
77
+ }
2
78
  /**
3
79
  * 关系型模型类
4
80
  * 继承自 BaseModel,提供关系型数据库特有的操作
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // 创建带软删除的模型
85
+ * const userModel = new RelatedModel(db, 'users', { softDelete: true });
86
+ *
87
+ * // 删除(实际执行 UPDATE SET deletedAt = NOW())
88
+ * await userModel.delete({ id: 1 });
89
+ *
90
+ * // 查询(自动排除已删除)
91
+ * await userModel.select('id', 'name');
92
+ *
93
+ * // 查询包含已删除
94
+ * await userModel.selectWithTrashed('id', 'name');
95
+ *
96
+ * // 恢复已删除
97
+ * await userModel.restore({ id: 1 });
98
+ * ```
5
99
  */
6
100
  export class RelatedModel extends Model {
7
- constructor(database, name) {
8
- super(database, name);
101
+ /** 关系定义存储 */
102
+ relations = new Map();
103
+ constructor(database, name, options) {
104
+ super(database, name, options);
105
+ }
106
+ // ============================================================================
107
+ // 关系定义方法
108
+ // ============================================================================
109
+ /**
110
+ * 定义一对多关系
111
+ * @param targetModel 目标模型实例
112
+ * @param foreignKey 目标表中的外键字段
113
+ * @param localKey 本表的主键字段(默认 'id')
114
+ * @example
115
+ * ```ts
116
+ * const userModel = db.model('users');
117
+ * const orderModel = db.model('orders');
118
+ *
119
+ * // User hasMany Orders (orders.userId -> users.id)
120
+ * userModel.hasMany(orderModel, 'userId');
121
+ * ```
122
+ */
123
+ hasMany(targetModel, foreignKey, localKey = 'id') {
124
+ const relationName = String(targetModel.name);
125
+ this.relations.set(relationName, {
126
+ type: 'hasMany',
127
+ target: targetModel.name,
128
+ foreignKey: foreignKey,
129
+ localKey,
130
+ });
131
+ return this;
132
+ }
133
+ /**
134
+ * 定义多对一关系
135
+ * @param targetModel 目标模型实例
136
+ * @param foreignKey 本表中的外键字段
137
+ * @param targetKey 目标表的主键字段(默认 'id')
138
+ * @example
139
+ * ```ts
140
+ * const userModel = db.model('users');
141
+ * const orderModel = db.model('orders');
142
+ *
143
+ * // Order belongsTo User (orders.userId -> users.id)
144
+ * orderModel.belongsTo(userModel, 'userId');
145
+ * ```
146
+ */
147
+ belongsTo(targetModel, foreignKey, targetKey = 'id') {
148
+ const relationName = String(targetModel.name);
149
+ this.relations.set(relationName, {
150
+ type: 'belongsTo',
151
+ target: targetModel.name,
152
+ foreignKey,
153
+ targetKey: targetKey,
154
+ });
155
+ return this;
156
+ }
157
+ /**
158
+ * 定义一对一关系
159
+ * @param targetModel 目标模型实例
160
+ * @param foreignKey 目标表中的外键字段
161
+ * @param localKey 本表的主键字段(默认 'id')
162
+ * @example
163
+ * ```ts
164
+ * const userModel = db.model('users');
165
+ * const profileModel = db.model('profiles');
166
+ *
167
+ * // User hasOne Profile (profiles.userId -> users.id)
168
+ * userModel.hasOne(profileModel, 'userId');
169
+ * ```
170
+ */
171
+ hasOne(targetModel, foreignKey, localKey = 'id') {
172
+ const relationName = String(targetModel.name);
173
+ this.relations.set(relationName, {
174
+ type: 'hasOne',
175
+ target: targetModel.name,
176
+ foreignKey: foreignKey,
177
+ localKey,
178
+ });
179
+ return this;
180
+ }
181
+ /**
182
+ * 定义多对多关系
183
+ * @param targetModel 目标模型实例
184
+ * @param pivotTable 中间表名
185
+ * @param foreignPivotKey 中间表中指向本表的外键
186
+ * @param relatedPivotKey 中间表中指向目标表的外键
187
+ * @param localKey 本表的主键字段(默认 'id')
188
+ * @param relatedKey 目标表的主键字段(默认 'id')
189
+ * @example
190
+ * ```ts
191
+ * const userModel = db.model('users');
192
+ * const roleModel = db.model('roles');
193
+ *
194
+ * // User belongsToMany Roles (通过 user_roles 中间表)
195
+ * userModel.belongsToMany(roleModel, 'user_roles', 'user_id', 'role_id');
196
+ *
197
+ * // 双向关系
198
+ * roleModel.belongsToMany(userModel, 'user_roles', 'role_id', 'user_id');
199
+ * ```
200
+ */
201
+ belongsToMany(targetModel, pivotTable, foreignPivotKey, relatedPivotKey, localKey = 'id', relatedKey = 'id', pivotFields) {
202
+ const relationName = String(targetModel.name);
203
+ this.relations.set(relationName, {
204
+ type: 'belongsToMany',
205
+ target: targetModel.name,
206
+ foreignKey: localKey,
207
+ targetKey: relatedKey,
208
+ localKey: localKey,
209
+ pivot: {
210
+ table: pivotTable,
211
+ foreignPivotKey,
212
+ relatedPivotKey,
213
+ pivotFields,
214
+ },
215
+ });
216
+ return this;
217
+ }
218
+ /**
219
+ * 获取关系定义
220
+ */
221
+ getRelation(name) {
222
+ return this.relations.get(name);
223
+ }
224
+ /**
225
+ * 获取所有关系名称
226
+ */
227
+ getRelationNames() {
228
+ return Array.from(this.relations.keys());
9
229
  }
230
+ // ============================================================================
231
+ // 关系查询方法
232
+ // ============================================================================
10
233
  /**
11
- * 创建数据
234
+ * 加载单条记录的关联数据
235
+ * @example
236
+ * ```ts
237
+ * const user = await userModel.selectById(1);
238
+ * const userWithPosts = await userModel.loadRelation(user, 'posts');
239
+ * // userWithPosts.posts = [{ id: 1, title: '...' }, ...]
240
+ * ```
241
+ */
242
+ async loadRelation(record, relationName) {
243
+ const relation = this.relations.get(relationName);
244
+ if (!relation) {
245
+ throw new Error(`Relation "${relationName}" not defined on model "${String(this.name)}"`);
246
+ }
247
+ const relatedData = await this.fetchRelatedData(record, relation);
248
+ return {
249
+ ...record,
250
+ [relationName]: relatedData,
251
+ };
252
+ }
253
+ /**
254
+ * 批量加载关联数据(预加载)
255
+ * @example
256
+ * ```ts
257
+ * const users = await userModel.select('id', 'name');
258
+ * const usersWithPosts = await userModel.loadRelations(users, ['posts']);
259
+ * ```
260
+ */
261
+ async loadRelations(records, relationNames) {
262
+ if (records.length === 0)
263
+ return [];
264
+ const result = [...records];
265
+ for (const relationName of relationNames) {
266
+ const relation = this.relations.get(relationName);
267
+ if (!relation) {
268
+ throw new Error(`Relation "${relationName}" not defined on model "${String(this.name)}"`);
269
+ }
270
+ await this.batchLoadRelation(result, relationName, relation);
271
+ }
272
+ return result;
273
+ }
274
+ /**
275
+ * 带关联的查询(链式调用入口)
276
+ * @example
277
+ * ```ts
278
+ * const users = await userModel.with('posts', 'profile')
279
+ * .where({ status: 'active' });
280
+ * ```
281
+ */
282
+ with(...relationNames) {
283
+ return new RelationQueryBuilder(this, relationNames);
284
+ }
285
+ // ============================================================================
286
+ // 内部方法
287
+ // ============================================================================
288
+ /**
289
+ * 获取单条记录的关联数据
290
+ */
291
+ async fetchRelatedData(record, relation) {
292
+ const targetDb = this.database;
293
+ switch (relation.type) {
294
+ case 'hasMany': {
295
+ const localValue = record[relation.localKey || 'id'];
296
+ const results = await targetDb.select(relation.target, [])
297
+ .where({ [relation.foreignKey]: localValue });
298
+ return results;
299
+ }
300
+ case 'hasOne': {
301
+ const localValue = record[relation.localKey || 'id'];
302
+ const results = await targetDb.select(relation.target, [])
303
+ .where({ [relation.foreignKey]: localValue })
304
+ .limit(1);
305
+ return results.length > 0 ? results[0] : null;
306
+ }
307
+ case 'belongsTo': {
308
+ const foreignValue = record[relation.foreignKey];
309
+ if (foreignValue === null || foreignValue === undefined) {
310
+ return null;
311
+ }
312
+ const results = await targetDb.select(relation.target, [])
313
+ .where({ [relation.targetKey || 'id']: foreignValue })
314
+ .limit(1);
315
+ return results.length > 0 ? results[0] : null;
316
+ }
317
+ case 'belongsToMany': {
318
+ if (!relation.pivot) {
319
+ throw new Error('belongsToMany relation requires pivot configuration');
320
+ }
321
+ const localKey = relation.localKey || 'id';
322
+ const localValue = record[localKey];
323
+ const { table: pivotTable, foreignPivotKey, relatedPivotKey, pivotFields } = relation.pivot;
324
+ const targetKey = relation.targetKey || 'id';
325
+ // 查询中间表获取关联的目标ID
326
+ const pivotRecords = await targetDb.query(`SELECT * FROM "${pivotTable}" WHERE "${foreignPivotKey}" = ?`, [localValue]);
327
+ if (pivotRecords.length === 0) {
328
+ return [];
329
+ }
330
+ // 获取目标表数据
331
+ const relatedIds = pivotRecords.map(p => p[relatedPivotKey]);
332
+ const relatedRecords = await targetDb.select(relation.target, [])
333
+ .where({ [targetKey]: { $in: relatedIds } });
334
+ // 如果需要包含 pivot 数据,将其附加到每条记录
335
+ if (pivotFields && pivotFields.length > 0) {
336
+ const pivotMap = new Map();
337
+ pivotRecords.forEach(p => pivotMap.set(p[relatedPivotKey], p));
338
+ return relatedRecords.map(r => ({
339
+ ...r,
340
+ pivot: pivotMap.get(r[targetKey]) || {}
341
+ }));
342
+ }
343
+ return relatedRecords;
344
+ }
345
+ default:
346
+ throw new Error(`Unknown relation type: ${relation.type}`);
347
+ }
348
+ }
349
+ /**
350
+ * 批量加载关联(优化 N+1 问题)
351
+ */
352
+ async batchLoadRelation(records, relationName, relation) {
353
+ const targetDb = this.database;
354
+ switch (relation.type) {
355
+ case 'hasMany':
356
+ case 'hasOne': {
357
+ // 收集所有本地主键值
358
+ const localKey = relation.localKey || 'id';
359
+ const localValues = records.map(r => r[localKey]).filter(v => v != null);
360
+ if (localValues.length === 0) {
361
+ records.forEach(r => r[relationName] = relation.type === 'hasMany' ? [] : null);
362
+ return;
363
+ }
364
+ // 一次性查询所有关联数据
365
+ const relatedRecords = await targetDb.select(relation.target, [])
366
+ .where({ [relation.foreignKey]: { $in: localValues } });
367
+ // 按外键分组
368
+ const grouped = new Map();
369
+ for (const related of relatedRecords) {
370
+ const fkValue = related[relation.foreignKey];
371
+ if (!grouped.has(fkValue)) {
372
+ grouped.set(fkValue, []);
373
+ }
374
+ grouped.get(fkValue).push(related);
375
+ }
376
+ // 分配给每条记录
377
+ for (const record of records) {
378
+ const localValue = record[localKey];
379
+ const related = grouped.get(localValue) || [];
380
+ record[relationName] = relation.type === 'hasMany' ? related : (related[0] || null);
381
+ }
382
+ break;
383
+ }
384
+ case 'belongsTo': {
385
+ // 收集所有外键值
386
+ const foreignValues = records
387
+ .map(r => r[relation.foreignKey])
388
+ .filter(v => v != null);
389
+ if (foreignValues.length === 0) {
390
+ records.forEach(r => r[relationName] = null);
391
+ return;
392
+ }
393
+ // 一次性查询所有关联数据
394
+ const targetKey = relation.targetKey || 'id';
395
+ const relatedRecords = await targetDb.select(relation.target, [])
396
+ .where({ [targetKey]: { $in: foreignValues } });
397
+ // 按主键索引
398
+ const indexed = new Map();
399
+ for (const related of relatedRecords) {
400
+ indexed.set(related[targetKey], related);
401
+ }
402
+ // 分配给每条记录
403
+ for (const record of records) {
404
+ const fkValue = record[relation.foreignKey];
405
+ record[relationName] = indexed.get(fkValue) || null;
406
+ }
407
+ break;
408
+ }
409
+ case 'belongsToMany': {
410
+ if (!relation.pivot) {
411
+ throw new Error('belongsToMany relation requires pivot configuration');
412
+ }
413
+ const localKey = relation.localKey || 'id';
414
+ const targetKey = relation.targetKey || 'id';
415
+ const { table: pivotTable, foreignPivotKey, relatedPivotKey, pivotFields } = relation.pivot;
416
+ // 收集所有本地主键值
417
+ const localValues = records.map(r => r[localKey]).filter(v => v != null);
418
+ if (localValues.length === 0) {
419
+ records.forEach(r => r[relationName] = []);
420
+ return;
421
+ }
422
+ // 批量查询中间表
423
+ const placeholders = localValues.map(() => '?').join(', ');
424
+ const pivotRecords = await targetDb.query(`SELECT * FROM "${pivotTable}" WHERE "${foreignPivotKey}" IN (${placeholders})`, localValues);
425
+ if (pivotRecords.length === 0) {
426
+ records.forEach(r => r[relationName] = []);
427
+ return;
428
+ }
429
+ // 获取所有相关的目标ID
430
+ const allRelatedIds = [...new Set(pivotRecords.map(p => p[relatedPivotKey]))];
431
+ // 批量查询目标表
432
+ const relatedRecords = await targetDb.select(relation.target, [])
433
+ .where({ [targetKey]: { $in: allRelatedIds } });
434
+ // 建立目标记录索引
435
+ const relatedIndex = new Map();
436
+ for (const related of relatedRecords) {
437
+ relatedIndex.set(related[targetKey], related);
438
+ }
439
+ // 按源ID分组中间表记录
440
+ const pivotGrouped = new Map();
441
+ for (const pivot of pivotRecords) {
442
+ const srcId = pivot[foreignPivotKey];
443
+ if (!pivotGrouped.has(srcId)) {
444
+ pivotGrouped.set(srcId, []);
445
+ }
446
+ pivotGrouped.get(srcId).push(pivot);
447
+ }
448
+ // 分配给每条记录
449
+ for (const record of records) {
450
+ const localValue = record[localKey];
451
+ const pivots = pivotGrouped.get(localValue) || [];
452
+ record[relationName] = pivots.map(pivot => {
453
+ const related = relatedIndex.get(pivot[relatedPivotKey]);
454
+ if (!related)
455
+ return null;
456
+ // 如果需要包含 pivot 数据
457
+ if (pivotFields && pivotFields.length > 0) {
458
+ const pivotData = {};
459
+ pivotFields.forEach(field => {
460
+ pivotData[field] = pivot[field];
461
+ });
462
+ return { ...related, pivot: pivotData };
463
+ }
464
+ return related;
465
+ }).filter(Boolean);
466
+ }
467
+ break;
468
+ }
469
+ }
470
+ }
471
+ // ============================================================================
472
+ // 带钩子的 CRUD 便捷方法
473
+ // ============================================================================
474
+ /**
475
+ * 创建数据(支持生命周期钩子)
476
+ * @example
477
+ * ```ts
478
+ * userModel.addHook('beforeCreate', (ctx) => {
479
+ * ctx.data.slug = slugify(ctx.data.name);
480
+ * });
481
+ * const user = await userModel.create({ name: 'John' });
482
+ * ```
12
483
  */
13
484
  async create(data) {
14
485
  if (!this.validateData(data)) {
15
486
  throw new Error('Invalid data provided');
16
487
  }
488
+ // 复制数据以避免修改原始对象
489
+ const inputData = { ...data };
490
+ // beforeCreate 钩子
491
+ const ctx = this.createHookContext({ data: inputData });
492
+ const shouldContinue = await this.runHooks('beforeCreate', ctx);
493
+ if (!shouldContinue) {
494
+ return null; // 钩子取消了操作
495
+ }
17
496
  try {
18
- const result = await this.database.insert(this.name, data);
19
- return result;
497
+ // 获取 insert 方法添加的时间戳
498
+ const insertData = { ...ctx.data };
499
+ await this.insert(insertData);
500
+ // 返回插入的数据(包括时间戳)
501
+ // 注意:SQLite INSERT 不返回实际数据,所以我们返回传入的数据
502
+ const result = insertData;
503
+ // afterCreate 钩子
504
+ ctx.result = result;
505
+ await this.runHooks('afterCreate', ctx);
506
+ return ctx.result;
20
507
  }
21
508
  catch (error) {
22
509
  this.handleError(error, 'create');
23
510
  }
24
511
  }
25
512
  /**
26
- * 批量创建数据
513
+ * 批量创建数据(每条数据都会触发钩子)
27
514
  */
28
515
  async createMany(data) {
29
516
  if (!Array.isArray(data) || data.length === 0) {
@@ -33,7 +520,9 @@ export class RelatedModel extends Model {
33
520
  const results = [];
34
521
  for (const item of data) {
35
522
  const result = await this.create(item);
36
- results.push(result);
523
+ if (result) {
524
+ results.push(result);
525
+ }
37
526
  }
38
527
  return results;
39
528
  }
@@ -42,39 +531,118 @@ export class RelatedModel extends Model {
42
531
  }
43
532
  }
44
533
  /**
45
- * 查找单个数据
534
+ * 查找单个数据(支持生命周期钩子)
535
+ * @example
536
+ * ```ts
537
+ * userModel.addHook('afterFind', (ctx) => {
538
+ * if (ctx.result) {
539
+ * ctx.result.fullName = ctx.result.firstName + ' ' + ctx.result.lastName;
540
+ * }
541
+ * });
542
+ * const user = await userModel.findOne({ id: 1 });
543
+ * ```
46
544
  */
47
- async selectOne(query) {
545
+ async findOne(query) {
546
+ // beforeFind 钩子
547
+ const ctx = this.createHookContext({ where: query });
548
+ const shouldContinue = await this.runHooks('beforeFind', ctx);
549
+ if (!shouldContinue) {
550
+ return null;
551
+ }
48
552
  try {
49
553
  const selection = this.select();
50
- if (query) {
51
- selection.where(query);
554
+ if (ctx.where) {
555
+ selection.where(ctx.where);
52
556
  }
53
557
  const results = await selection.limit(1);
54
- return results.length > 0 ? results[0] : null;
558
+ const result = results.length > 0 ? results[0] : null;
559
+ // afterFind 钩子
560
+ ctx.result = result ?? undefined;
561
+ await this.runHooks('afterFind', ctx);
562
+ return ctx.result ?? null;
55
563
  }
56
564
  catch (error) {
57
- this.handleError(error, 'selectOne');
565
+ this.handleError(error, 'findOne');
58
566
  }
59
567
  }
568
+ /**
569
+ * 查找多条数据(支持生命周期钩子)
570
+ */
571
+ async findAll(query) {
572
+ // beforeFind 钩子
573
+ const ctx = this.createHookContext({ where: query });
574
+ const shouldContinue = await this.runHooks('beforeFind', ctx);
575
+ if (!shouldContinue) {
576
+ return [];
577
+ }
578
+ try {
579
+ const selection = this.select();
580
+ if (ctx.where) {
581
+ selection.where(ctx.where);
582
+ }
583
+ const results = await selection;
584
+ // afterFind 钩子
585
+ ctx.result = results;
586
+ await this.runHooks('afterFind', ctx);
587
+ return ctx.result ?? [];
588
+ }
589
+ catch (error) {
590
+ this.handleError(error, 'findAll');
591
+ }
592
+ }
593
+ /**
594
+ * selectOne 的别名(向后兼容)
595
+ */
596
+ async selectOne(query) {
597
+ return this.findOne(query);
598
+ }
60
599
  /**
61
600
  * 根据ID查找
62
601
  */
63
602
  async selectById(id) {
64
- return this.selectOne({ id });
603
+ return this.findOne({ id });
65
604
  }
66
605
  /**
67
- * 更新单个数据
606
+ * findById 别名
68
607
  */
69
- async updateOne(query, data) {
608
+ async findById(id) {
609
+ return this.findOne({ id });
610
+ }
611
+ /**
612
+ * 更新数据(支持生命周期钩子)
613
+ * @example
614
+ * ```ts
615
+ * userModel.addHook('beforeUpdate', (ctx) => {
616
+ * ctx.data.updatedAt = new Date();
617
+ * });
618
+ * await userModel.updateWhere({ role: 'guest' }, { role: 'member' });
619
+ * ```
620
+ */
621
+ async updateWhere(query, data) {
622
+ // beforeUpdate 钩子
623
+ const ctx = this.createHookContext({ where: query, data });
624
+ const shouldContinue = await this.runHooks('beforeUpdate', ctx);
625
+ if (!shouldContinue) {
626
+ return 0;
627
+ }
70
628
  try {
71
- const result = await this.update(data).where(query);
72
- return result > 0;
629
+ const result = await this.update(ctx.data).where(ctx.where);
630
+ // afterUpdate 钩子
631
+ ctx.result = result;
632
+ await this.runHooks('afterUpdate', ctx);
633
+ return result;
73
634
  }
74
635
  catch (error) {
75
- this.handleError(error, 'updateOne');
636
+ this.handleError(error, 'updateWhere');
76
637
  }
77
638
  }
639
+ /**
640
+ * 更新单个数据(向后兼容)
641
+ */
642
+ async updateOne(query, data) {
643
+ const result = await this.updateWhere(query, data);
644
+ return result > 0;
645
+ }
78
646
  /**
79
647
  * 根据ID更新
80
648
  */
@@ -82,10 +650,67 @@ export class RelatedModel extends Model {
82
650
  return this.updateOne({ id }, data);
83
651
  }
84
652
  /**
85
- * 根据ID删除
653
+ * 删除数据(支持生命周期钩子和软删除)
654
+ * @example
655
+ * ```ts
656
+ * userModel.addHook('beforeDelete', async (ctx) => {
657
+ * // 删除前检查
658
+ * const user = await userModel.findOne(ctx.where);
659
+ * if (user?.role === 'admin') return false; // 取消删除
660
+ * });
661
+ * await userModel.deleteWhere({ status: 'inactive' });
662
+ * ```
663
+ */
664
+ async deleteWhere(query) {
665
+ // beforeDelete 钩子
666
+ const ctx = this.createHookContext({ where: query });
667
+ const shouldContinue = await this.runHooks('beforeDelete', ctx);
668
+ if (!shouldContinue) {
669
+ return this.isSoftDelete ? 0 : [];
670
+ }
671
+ try {
672
+ const result = await this.delete(ctx.where);
673
+ // afterDelete 钩子
674
+ ctx.result = result;
675
+ await this.runHooks('afterDelete', ctx);
676
+ return result;
677
+ }
678
+ catch (error) {
679
+ this.handleError(error, 'deleteWhere');
680
+ }
681
+ }
682
+ /**
683
+ * 根据ID删除(支持软删除和钩子)
86
684
  */
87
685
  async deleteById(id) {
88
- const result = await this.delete({ id });
686
+ const result = await this.deleteWhere({ id });
687
+ if (typeof result === 'number') {
688
+ return result > 0;
689
+ }
690
+ return result.length > 0;
691
+ }
692
+ /**
693
+ * 根据ID强制删除(物理删除,忽略软删除设置,但仍触发钩子)
694
+ */
695
+ async forceDeleteById(id) {
696
+ const query = { id };
697
+ // beforeDelete 钩子
698
+ const ctx = this.createHookContext({ where: query });
699
+ const shouldContinue = await this.runHooks('beforeDelete', ctx);
700
+ if (!shouldContinue) {
701
+ return false;
702
+ }
703
+ const result = await this.forceDelete(ctx.where);
704
+ // afterDelete 钩子
705
+ ctx.result = result;
706
+ await this.runHooks('afterDelete', ctx);
707
+ return result.length > 0;
708
+ }
709
+ /**
710
+ * 根据ID恢复软删除的记录
711
+ */
712
+ async restoreById(id) {
713
+ const result = await this.restore({ id });
89
714
  return result > 0;
90
715
  }
91
716
  /**
@@ -93,7 +718,7 @@ export class RelatedModel extends Model {
93
718
  */
94
719
  async count(query) {
95
720
  try {
96
- const selection = this.select('*');
721
+ const selection = this.select();
97
722
  if (query) {
98
723
  selection.where(query);
99
724
  }