@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.
- 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 +12 -7
- package/lib/dialects/memory.d.ts.map +1 -1
- package/lib/dialects/memory.js +7 -4
- package/lib/dialects/memory.js.map +1 -1
- package/lib/dialects/mongodb.d.ts +11 -7
- package/lib/dialects/mongodb.d.ts.map +1 -1
- package/lib/dialects/mongodb.js +18 -15
- package/lib/dialects/mongodb.js.map +1 -1
- package/lib/dialects/mysql.d.ts +35 -6
- package/lib/dialects/mysql.d.ts.map +1 -1
- package/lib/dialects/mysql.js +137 -18
- package/lib/dialects/mysql.js.map +1 -1
- package/lib/dialects/pg.d.ts +35 -6
- package/lib/dialects/pg.d.ts.map +1 -1
- package/lib/dialects/pg.js +137 -18
- package/lib/dialects/pg.js.map +1 -1
- package/lib/dialects/redis.d.ts +11 -6
- package/lib/dialects/redis.d.ts.map +1 -1
- package/lib/dialects/redis.js +11 -8
- package/lib/dialects/redis.js.map +1 -1
- package/lib/dialects/sqlite.d.ts +19 -6
- package/lib/dialects/sqlite.d.ts.map +1 -1
- package/lib/dialects/sqlite.js +63 -10
- 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 +11 -11
- package/lib/type/document/database.d.ts.map +1 -1
- package/lib/type/document/database.js.map +1 -1
- package/lib/type/document/model.d.ts +7 -7
- package/lib/type/document/model.d.ts.map +1 -1
- package/lib/type/document/model.js.map +1 -1
- package/lib/type/keyvalue/database.d.ts +11 -11
- package/lib/type/keyvalue/database.d.ts.map +1 -1
- package/lib/type/keyvalue/database.js.map +1 -1
- package/lib/type/keyvalue/model.d.ts +2 -2
- package/lib/type/keyvalue/model.d.ts.map +1 -1
- package/lib/type/keyvalue/model.js.map +1 -1
- package/lib/type/related/database.d.ts +48 -13
- package/lib/type/related/database.d.ts.map +1 -1
- package/lib/type/related/database.js +258 -27
- package/lib/type/related/database.js.map +1 -1
- package/lib/type/related/model.d.ts +251 -15
- package/lib/type/related/model.d.ts.map +1 -1
- package/lib/type/related/model.js +647 -22
- 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 +14 -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 +14 -13
- package/src/dialects/mongodb.ts +40 -38
- package/src/dialects/mysql.ts +151 -22
- package/src/dialects/pg.ts +148 -21
- package/src/dialects/redis.ts +40 -38
- package/src/dialects/sqlite.ts +73 -15
- package/src/index.ts +1 -2
- package/src/migration.ts +544 -0
- package/src/registry.ts +33 -33
- package/src/type/document/database.ts +32 -32
- package/src/type/document/model.ts +14 -14
- package/src/type/keyvalue/database.ts +32 -32
- package/src/type/keyvalue/model.ts +18 -18
- package/src/type/related/database.ts +309 -34
- package/src/type/related/model.ts +800 -33
- package/src/types.ts +559 -44
- package/tests/database.test.ts +1738 -0
package/src/base/model.ts
CHANGED
|
@@ -1,23 +1,148 @@
|
|
|
1
1
|
import type { Database } from './database.js';
|
|
2
2
|
import type { Dialect } from './dialect.js';
|
|
3
|
-
import { AlterDefinition, Condition } from '../types.js';
|
|
3
|
+
import { AlterDefinition, Condition, ModelOptions, HookName, HookFn, HookContext, HooksConfig } from '../types.js';
|
|
4
4
|
import * as QueryClasses from './query-classes.js';
|
|
5
5
|
|
|
6
|
+
/** 默认软删除字段名 */
|
|
7
|
+
const DEFAULT_DELETED_AT = 'deletedAt';
|
|
8
|
+
/** 默认创建时间字段名 */
|
|
9
|
+
const DEFAULT_CREATED_AT = 'createdAt';
|
|
10
|
+
/** 默认更新时间字段名 */
|
|
11
|
+
const DEFAULT_UPDATED_AT = 'updatedAt';
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* 基础模型抽象类
|
|
9
15
|
* 定义所有模型类型的通用接口和行为
|
|
16
|
+
* 支持软删除、自动时间戳和生命周期钩子
|
|
10
17
|
*/
|
|
11
|
-
export abstract class Model<C=any,
|
|
18
|
+
export abstract class Model<C=any,S extends Record<string,object>=Record<string, object>,Q = string,T extends keyof S=keyof S> {
|
|
19
|
+
/** 模型配置选项 */
|
|
20
|
+
public readonly options: ModelOptions;
|
|
21
|
+
/** 生命周期钩子注册表 */
|
|
22
|
+
private readonly hooks: Map<HookName, HookFn<S[T]>[]> = new Map();
|
|
23
|
+
|
|
12
24
|
constructor(
|
|
13
|
-
public readonly database: Database<
|
|
14
|
-
public readonly name:
|
|
15
|
-
|
|
25
|
+
public readonly database: Database<any, S, Q>,
|
|
26
|
+
public readonly name: T,
|
|
27
|
+
options?: ModelOptions
|
|
28
|
+
) {
|
|
29
|
+
this.options = options ?? {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Lifecycle Hooks
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 注册生命周期钩子
|
|
38
|
+
* @param hookName 钩子名称
|
|
39
|
+
* @param fn 钩子函数
|
|
40
|
+
* @returns this(支持链式调用)
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* userModel
|
|
45
|
+
* .addHook('beforeCreate', (ctx) => {
|
|
46
|
+
* ctx.data.createdBy = 'system';
|
|
47
|
+
* })
|
|
48
|
+
* .addHook('afterDelete', async (ctx) => {
|
|
49
|
+
* await logService.log('User deleted', ctx.result);
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
addHook(hookName: HookName, fn: HookFn<S[T]>): this {
|
|
54
|
+
const fns = this.hooks.get(hookName) ?? [];
|
|
55
|
+
fns.push(fn);
|
|
56
|
+
this.hooks.set(hookName, fns);
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* addHook 的别名
|
|
62
|
+
*/
|
|
63
|
+
on(hookName: HookName, fn: HookFn<S[T]>): this {
|
|
64
|
+
return this.addHook(hookName, fn);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 批量注册钩子
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* userModel.registerHooks({
|
|
72
|
+
* beforeCreate: (ctx) => { ... },
|
|
73
|
+
* afterUpdate: [(ctx) => { ... }, (ctx) => { ... }]
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
registerHooks(hooks: HooksConfig<S[T]>): this {
|
|
78
|
+
for (const [name, fn] of Object.entries(hooks)) {
|
|
79
|
+
if (Array.isArray(fn)) {
|
|
80
|
+
fn.forEach(f => this.addHook(name as HookName, f));
|
|
81
|
+
} else if (fn) {
|
|
82
|
+
this.addHook(name as HookName, fn);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 移除指定钩子
|
|
90
|
+
*/
|
|
91
|
+
removeHook(hookName: HookName, fn?: HookFn<S[T]>): this {
|
|
92
|
+
if (!fn) {
|
|
93
|
+
this.hooks.delete(hookName);
|
|
94
|
+
} else {
|
|
95
|
+
const fns = this.hooks.get(hookName);
|
|
96
|
+
if (fns) {
|
|
97
|
+
const index = fns.indexOf(fn);
|
|
98
|
+
if (index > -1) {
|
|
99
|
+
fns.splice(index, 1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 清除所有钩子
|
|
108
|
+
*/
|
|
109
|
+
clearHooks(): this {
|
|
110
|
+
this.hooks.clear();
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 触发钩子
|
|
116
|
+
* @returns 如果任何 before 钩子返回 false,则返回 false
|
|
117
|
+
*/
|
|
118
|
+
protected async runHooks(hookName: HookName, context: HookContext<S[T]>): Promise<boolean> {
|
|
119
|
+
const fns = this.hooks.get(hookName);
|
|
120
|
+
if (!fns || fns.length === 0) return true;
|
|
121
|
+
|
|
122
|
+
for (const fn of fns) {
|
|
123
|
+
const result = await fn(context);
|
|
124
|
+
// before 钩子返回 false 可以取消操作
|
|
125
|
+
if (hookName.startsWith('before') && result === false) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 创建钩子上下文
|
|
134
|
+
*/
|
|
135
|
+
protected createHookContext(options: Partial<HookContext<S[T]>> = {}): HookContext<S[T]> {
|
|
136
|
+
return {
|
|
137
|
+
modelName: String(this.name),
|
|
138
|
+
...options
|
|
139
|
+
};
|
|
140
|
+
}
|
|
16
141
|
|
|
17
142
|
/**
|
|
18
143
|
* 获取数据库方言
|
|
19
144
|
*/
|
|
20
|
-
get dialect(): Dialect<C,Q> {
|
|
145
|
+
get dialect(): Dialect<C,S,Q> {
|
|
21
146
|
return this.database.dialect;
|
|
22
147
|
}
|
|
23
148
|
|
|
@@ -31,28 +156,143 @@ export abstract class Model<C=any,O extends object = object,Q = string> {
|
|
|
31
156
|
/**
|
|
32
157
|
* 获取模型名称
|
|
33
158
|
*/
|
|
34
|
-
get modelName():
|
|
159
|
+
get modelName(): T {
|
|
35
160
|
return this.name;
|
|
36
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 是否启用软删除
|
|
165
|
+
*/
|
|
166
|
+
get isSoftDelete(): boolean {
|
|
167
|
+
return this.options.softDelete === true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 软删除字段名
|
|
172
|
+
*/
|
|
173
|
+
get deletedAtField(): string {
|
|
174
|
+
return this.options.deletedAtField ?? DEFAULT_DELETED_AT;
|
|
175
|
+
}
|
|
37
176
|
|
|
38
|
-
alter(alterations: AlterDefinition<
|
|
39
|
-
return this.database.alter<
|
|
177
|
+
alter(alterations: AlterDefinition<S[T]>): QueryClasses.Alteration<S,T, C, Q> {
|
|
178
|
+
return this.database.alter<T>(this.name, alterations);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 查询(软删除模式下自动排除已删除记录)
|
|
183
|
+
*/
|
|
184
|
+
select<K extends keyof S[T]>(...fields: Array<K>): QueryClasses.Selection<S,T,K, C, Q> {
|
|
185
|
+
const selection = this.database.select<T, K>(this.name, fields);
|
|
186
|
+
// 软删除模式下自动添加条件
|
|
187
|
+
if (this.isSoftDelete) {
|
|
188
|
+
selection.where({ [this.deletedAtField]: null } as any);
|
|
189
|
+
}
|
|
190
|
+
return selection;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 查询(包含已删除的记录)
|
|
195
|
+
*/
|
|
196
|
+
selectWithTrashed<K extends keyof S[T]>(...fields: Array<K>): QueryClasses.Selection<S,T,K, C, Q> {
|
|
197
|
+
return this.database.select<T, K>(this.name, fields);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 仅查询已删除的记录
|
|
202
|
+
*/
|
|
203
|
+
selectOnlyTrashed<K extends keyof S[T]>(...fields: Array<K>): QueryClasses.Selection<S,T,K, C, Q> {
|
|
204
|
+
const selection = this.database.select<T, K>(this.name, fields);
|
|
205
|
+
selection.where({ [this.deletedAtField]: { $ne: null } } as any);
|
|
206
|
+
return selection;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
insert(data: S[T]): QueryClasses.Insertion<S,T, C, Q> {
|
|
210
|
+
// 自动添加时间戳
|
|
211
|
+
if (this.options.timestamps) {
|
|
212
|
+
const now = new Date();
|
|
213
|
+
const createdAtField = this.options.createdAtField ?? DEFAULT_CREATED_AT;
|
|
214
|
+
const updatedAtField = this.options.updatedAtField ?? DEFAULT_UPDATED_AT;
|
|
215
|
+
(data as any)[createdAtField] = now;
|
|
216
|
+
(data as any)[updatedAtField] = now;
|
|
217
|
+
}
|
|
218
|
+
return this.database.insert<T>(this.name, data);
|
|
40
219
|
}
|
|
41
|
-
|
|
42
|
-
|
|
220
|
+
|
|
221
|
+
update(update: Partial<S[T]>): QueryClasses.Updation<S,T, C, Q> {
|
|
222
|
+
// 自动更新时间戳
|
|
223
|
+
if (this.options.timestamps) {
|
|
224
|
+
const updatedAtField = this.options.updatedAtField ?? DEFAULT_UPDATED_AT;
|
|
225
|
+
(update as any)[updatedAtField] = new Date();
|
|
226
|
+
}
|
|
227
|
+
const updation = this.database.update<T>(this.name, update);
|
|
228
|
+
// 软删除模式下只更新未删除的记录
|
|
229
|
+
if (this.isSoftDelete) {
|
|
230
|
+
updation.where({ [this.deletedAtField]: null } as any);
|
|
231
|
+
}
|
|
232
|
+
return updation;
|
|
43
233
|
}
|
|
44
234
|
|
|
45
|
-
|
|
46
|
-
|
|
235
|
+
/**
|
|
236
|
+
* 删除(软删除模式下执行软删除)
|
|
237
|
+
*/
|
|
238
|
+
delete(condition: Condition<S[T]>): QueryClasses.Deletion<S,T, C, Q> | QueryClasses.Updation<S,T, C, Q> {
|
|
239
|
+
if (this.isSoftDelete) {
|
|
240
|
+
// 软删除:UPDATE SET deletedAt = NOW()
|
|
241
|
+
return this.database.update<T>(this.name, {
|
|
242
|
+
[this.deletedAtField]: new Date()
|
|
243
|
+
} as any).where(condition);
|
|
244
|
+
}
|
|
245
|
+
return this.database.delete<T>(this.name, condition);
|
|
47
246
|
}
|
|
48
247
|
|
|
49
|
-
|
|
50
|
-
|
|
248
|
+
/**
|
|
249
|
+
* 强制删除(忽略软删除,直接物理删除)
|
|
250
|
+
*/
|
|
251
|
+
forceDelete(condition: Condition<S[T]>): QueryClasses.Deletion<S,T, C, Q> {
|
|
252
|
+
return this.database.delete<T>(this.name, condition);
|
|
51
253
|
}
|
|
52
254
|
|
|
53
|
-
|
|
54
|
-
|
|
255
|
+
/**
|
|
256
|
+
* 恢复软删除的记录
|
|
257
|
+
*/
|
|
258
|
+
restore(condition: Condition<S[T]>): QueryClasses.Updation<S,T, C, Q> {
|
|
259
|
+
if (!this.isSoftDelete) {
|
|
260
|
+
throw new Error(`Model ${String(this.name)} does not have soft delete enabled`);
|
|
261
|
+
}
|
|
262
|
+
return this.database.update<T>(this.name, {
|
|
263
|
+
[this.deletedAtField]: null
|
|
264
|
+
} as any).where(condition);
|
|
55
265
|
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 批量插入
|
|
269
|
+
*/
|
|
270
|
+
insertMany(data: S[T][]): QueryClasses.BatchInsertion<S,T, C, Q> {
|
|
271
|
+
// 自动添加时间戳
|
|
272
|
+
if (this.options.timestamps) {
|
|
273
|
+
const now = new Date();
|
|
274
|
+
const createdAtField = this.options.createdAtField ?? DEFAULT_CREATED_AT;
|
|
275
|
+
const updatedAtField = this.options.updatedAtField ?? DEFAULT_UPDATED_AT;
|
|
276
|
+
data.forEach(item => {
|
|
277
|
+
(item as any)[createdAtField] = now;
|
|
278
|
+
(item as any)[updatedAtField] = now;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return this.database.insertMany<T>(this.name, data);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 聚合查询
|
|
286
|
+
*/
|
|
287
|
+
aggregate(): QueryClasses.Aggregation<S,T, C, Q> {
|
|
288
|
+
const agg = this.database.aggregate<T>(this.name);
|
|
289
|
+
// 软删除模式下自动排除已删除记录
|
|
290
|
+
if (this.isSoftDelete) {
|
|
291
|
+
agg.where({ [this.deletedAtField]: null } as any);
|
|
292
|
+
}
|
|
293
|
+
return agg;
|
|
294
|
+
}
|
|
295
|
+
|
|
56
296
|
/**
|
|
57
297
|
* 验证查询条件
|
|
58
298
|
*/
|
|
@@ -71,7 +311,7 @@ export abstract class Model<C=any,O extends object = object,Q = string> {
|
|
|
71
311
|
* 处理错误
|
|
72
312
|
*/
|
|
73
313
|
protected handleError(error: Error, operation: string): never {
|
|
74
|
-
const message = `Model ${this.name} ${operation} failed: ${error.message}`;
|
|
314
|
+
const message = `Model ${String(this.name)} ${operation} failed: ${error.message}`;
|
|
75
315
|
throw new Error(message);
|
|
76
316
|
}
|
|
77
317
|
}
|