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