befly 3.8.34 → 3.9.0
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/befly.config.ts +1 -1
- package/hooks/parser.ts +2 -2
- package/hooks/permission.ts +1 -2
- package/lib/cacheHelper.ts +1 -2
- package/lib/cipher.ts +4 -3
- package/lib/dbHelper.ts +17 -41
- package/lib/jwt.ts +13 -5
- package/lib/redisHelper.ts +8 -9
- package/lib/sqlBuilder.ts +1 -1
- package/lib/validator.ts +2 -1
- package/loader/loadHooks.ts +1 -1
- package/loader/loadPlugins.ts +1 -1
- package/main.ts +4 -4
- package/package.json +3 -3
- package/sync/syncDb/apply.ts +2 -2
- package/sync/syncDb/ddl.ts +2 -1
- package/sync/syncDb/schema.ts +5 -2
- package/sync/syncDb/sqlite.ts +3 -2
- package/sync/syncDb/table.ts +14 -14
- package/sync/syncDb/tableCreate.ts +2 -2
- package/sync/syncDb/types.ts +9 -6
- package/sync/syncDb.ts +1 -1
- package/sync/syncDev.ts +1 -3
- package/sync/syncMenu.ts +1 -1
- package/tests/cacheHelper.test.ts +2 -2
- package/tests/cipher.test.ts +4 -4
- package/tests/syncDb-types.test.ts +6 -2
- package/tsconfig.json +1 -2
- package/types/befly.d.ts +8 -8
- package/types/common.d.ts +49 -0
- package/types/database.d.ts +24 -4
- package/types/index.ts +9 -4
- package/types/plugin.d.ts +5 -0
- package/types/sync.d.ts +2 -1
package/befly.config.ts
CHANGED
package/hooks/parser.ts
CHANGED
|
@@ -36,7 +36,7 @@ const hook: Hook = {
|
|
|
36
36
|
try {
|
|
37
37
|
// JSON 格式
|
|
38
38
|
if (contentType.includes('application/json')) {
|
|
39
|
-
const body = await ctx.req.json()
|
|
39
|
+
const body = (await ctx.req.json()) as Record<string, any>;
|
|
40
40
|
if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
|
|
41
41
|
ctx.body = pickFields(body, Object.keys(ctx.api.fields));
|
|
42
42
|
} else {
|
|
@@ -45,7 +45,7 @@ const hook: Hook = {
|
|
|
45
45
|
} else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
|
|
46
46
|
// XML 格式
|
|
47
47
|
const text = await ctx.req.text();
|
|
48
|
-
const body = xmlParser.parse(text)
|
|
48
|
+
const body = xmlParser.parse(text) as Record<string, any>;
|
|
49
49
|
if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
|
|
50
50
|
ctx.body = pickFields(body, Object.keys(ctx.api.fields));
|
|
51
51
|
} else {
|
package/hooks/permission.ts
CHANGED
|
@@ -40,8 +40,7 @@ const hook: Hook = {
|
|
|
40
40
|
// 验证角色权限
|
|
41
41
|
const apiPath = `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
|
|
42
42
|
const roleApisKey = RedisKeys.roleApis(ctx.user.roleCode);
|
|
43
|
-
|
|
44
|
-
hasPermission = isMember === 1;
|
|
43
|
+
hasPermission = await befly.redis.sismember(roleApisKey, apiPath);
|
|
45
44
|
} catch (error) {
|
|
46
45
|
// Redis 异常时降级为拒绝访问
|
|
47
46
|
befly.logger.warn({ err: error, route: ctx.route }, 'Redis 权限检查失败');
|
package/lib/cacheHelper.ts
CHANGED
|
@@ -225,8 +225,7 @@ export class CacheHelper {
|
|
|
225
225
|
*/
|
|
226
226
|
async checkRolePermission(roleCode: string, apiPath: string): Promise<boolean> {
|
|
227
227
|
try {
|
|
228
|
-
|
|
229
|
-
return result === 1;
|
|
228
|
+
return await this.befly.redis.sismember(RedisKeys.roleApis(roleCode), apiPath);
|
|
230
229
|
} catch (error: any) {
|
|
231
230
|
Logger.error({ err: error, roleCode: roleCode }, '检查角色权限失败');
|
|
232
231
|
return false;
|
package/lib/cipher.ts
CHANGED
|
@@ -77,10 +77,10 @@ export class Cipher {
|
|
|
77
77
|
* RSA-SHA256 签名
|
|
78
78
|
* @param data - 要签名的数据
|
|
79
79
|
* @param privateKey - 私钥
|
|
80
|
-
* @param encoding -
|
|
80
|
+
* @param encoding - 输出编码('hex' | 'base64' | 'base64url')
|
|
81
81
|
* @returns RSA-SHA256 签名
|
|
82
82
|
*/
|
|
83
|
-
static rsaSha256(data: string, privateKey: string | Buffer, encoding:
|
|
83
|
+
static rsaSha256(data: string, privateKey: string | Buffer, encoding: 'hex' | 'base64' | 'base64url' = 'hex'): string {
|
|
84
84
|
const sign = createSign('RSA-SHA256');
|
|
85
85
|
sign.update(data);
|
|
86
86
|
const signature = sign.sign(privateKey, encoding);
|
|
@@ -253,6 +253,7 @@ export class Cipher {
|
|
|
253
253
|
* @returns 64位哈希值
|
|
254
254
|
*/
|
|
255
255
|
static fastHash(data: string | Uint8Array, seed: number = 0): number {
|
|
256
|
-
|
|
256
|
+
const result = Bun.hash(data, seed);
|
|
257
|
+
return typeof result === 'bigint' ? Number(result) : result;
|
|
257
258
|
}
|
|
258
259
|
}
|
package/lib/dbHelper.ts
CHANGED
|
@@ -86,7 +86,7 @@ export class DbHelper {
|
|
|
86
86
|
private async getTableColumns(table: string): Promise<string[]> {
|
|
87
87
|
// 1. 先查 Redis 缓存
|
|
88
88
|
const cacheKey = RedisKeys.tableColumns(table);
|
|
89
|
-
|
|
89
|
+
const columns = await this.befly.redis.getObject<string[]>(cacheKey);
|
|
90
90
|
|
|
91
91
|
if (columns && columns.length > 0) {
|
|
92
92
|
return columns;
|
|
@@ -100,12 +100,12 @@ export class DbHelper {
|
|
|
100
100
|
throw new Error(`表 ${table} 不存在或没有字段`);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
const columnNames = result.map((row: any) => row.Field) as string[];
|
|
104
104
|
|
|
105
105
|
// 3. 写入 Redis 缓存
|
|
106
|
-
await this.befly.redis.setObject(cacheKey,
|
|
106
|
+
await this.befly.redis.setObject(cacheKey, columnNames, RedisTTL.tableColumns);
|
|
107
107
|
|
|
108
|
-
return
|
|
108
|
+
return columnNames;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/**
|
|
@@ -171,7 +171,7 @@ export class DbHelper {
|
|
|
171
171
|
* 统一的查询参数预处理方法
|
|
172
172
|
*/
|
|
173
173
|
private async prepareQueryOptions(options: QueryOptions) {
|
|
174
|
-
const cleanWhere = this.cleanFields(options.where);
|
|
174
|
+
const cleanWhere = this.cleanFields(options.where || {});
|
|
175
175
|
|
|
176
176
|
// 处理 fields(支持排除语法)
|
|
177
177
|
const processedFields = await this.fieldsToSnake(snakeCase(options.table), options.fields || []);
|
|
@@ -401,7 +401,7 @@ export class DbHelper {
|
|
|
401
401
|
* getOne({ table: 'userProfile', fields: ['userId', 'userName'] })
|
|
402
402
|
* getOne({ table: 'user_profile', fields: ['user_id', 'user_name'] })
|
|
403
403
|
*/
|
|
404
|
-
async getOne<T = any
|
|
404
|
+
async getOne<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<T | null> {
|
|
405
405
|
const { table, fields, where } = await this.prepareQueryOptions(options);
|
|
406
406
|
|
|
407
407
|
const builder = new SqlBuilder().select(fields).from(table).where(this.addDefaultStateFilter(where));
|
|
@@ -427,7 +427,7 @@ export class DbHelper {
|
|
|
427
427
|
* // 使用小驼峰格式(推荐)
|
|
428
428
|
* getList({ table: 'userProfile', fields: ['userId', 'userName', 'createdAt'] })
|
|
429
429
|
*/
|
|
430
|
-
async getList<T = any
|
|
430
|
+
async getList<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<ListResult<T>> {
|
|
431
431
|
const prepared = await this.prepareQueryOptions(options);
|
|
432
432
|
|
|
433
433
|
// 参数上限校验
|
|
@@ -451,7 +451,7 @@ export class DbHelper {
|
|
|
451
451
|
// 如果总数为 0,直接返回,不执行第二次查询
|
|
452
452
|
if (total === 0) {
|
|
453
453
|
return {
|
|
454
|
-
|
|
454
|
+
list: [],
|
|
455
455
|
total: 0,
|
|
456
456
|
page: prepared.page,
|
|
457
457
|
limit: prepared.limit,
|
|
@@ -476,7 +476,7 @@ export class DbHelper {
|
|
|
476
476
|
|
|
477
477
|
// 转换 BIGINT 字段(id, pid 等)为数字类型
|
|
478
478
|
return {
|
|
479
|
-
|
|
479
|
+
list: this.convertBigIntFields<T>(camelList),
|
|
480
480
|
total: total,
|
|
481
481
|
page: prepared.page,
|
|
482
482
|
limit: prepared.limit,
|
|
@@ -493,7 +493,7 @@ export class DbHelper {
|
|
|
493
493
|
* // 使用小驼峰格式(推荐)
|
|
494
494
|
* getAll({ table: 'userProfile', fields: ['userId', 'userName'] })
|
|
495
495
|
*/
|
|
496
|
-
async getAll<T = any
|
|
496
|
+
async getAll<T extends Record<string, any> = Record<string, any>>(options: Omit<QueryOptions, 'page' | 'limit'>): Promise<T[]> {
|
|
497
497
|
// 添加硬性上限保护,防止内存溢出
|
|
498
498
|
const MAX_LIMIT = 10000;
|
|
499
499
|
const WARNING_LIMIT = 1000;
|
|
@@ -745,6 +745,7 @@ export class DbHelper {
|
|
|
745
745
|
|
|
746
746
|
/**
|
|
747
747
|
* 执行事务
|
|
748
|
+
* 使用 Bun SQL 的 begin 方法开启事务
|
|
748
749
|
*/
|
|
749
750
|
async trans<T = any>(callback: TransactionCallback<T>): Promise<T> {
|
|
750
751
|
if (this.isTransaction) {
|
|
@@ -752,37 +753,12 @@ export class DbHelper {
|
|
|
752
753
|
return await callback(this);
|
|
753
754
|
}
|
|
754
755
|
|
|
755
|
-
//
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const result = await callback(trans);
|
|
762
|
-
|
|
763
|
-
// 提交事务
|
|
764
|
-
try {
|
|
765
|
-
await conn.query('COMMIT');
|
|
766
|
-
committed = true;
|
|
767
|
-
} catch (commitError: any) {
|
|
768
|
-
Logger.error({ err: commitError }, '事务提交失败,正在回滚');
|
|
769
|
-
await conn.query('ROLLBACK');
|
|
770
|
-
throw new Error(`事务提交失败: ${commitError.message}`);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
return result;
|
|
774
|
-
} catch (error: any) {
|
|
775
|
-
// 回调执行失败,回滚事务
|
|
776
|
-
if (!committed) {
|
|
777
|
-
try {
|
|
778
|
-
await conn.query('ROLLBACK');
|
|
779
|
-
Logger.warn('事务已回滚');
|
|
780
|
-
} catch (rollbackError: any) {
|
|
781
|
-
Logger.error({ err: rollbackError }, '事务回滚失败');
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
throw error;
|
|
785
|
-
}
|
|
756
|
+
// 使用 Bun SQL 的 begin 方法开启事务
|
|
757
|
+
// begin 方法会自动处理 commit/rollback
|
|
758
|
+
return await this.sql.begin(async (tx: any) => {
|
|
759
|
+
const trans = new DbHelper(this.befly, tx);
|
|
760
|
+
return await callback(trans);
|
|
761
|
+
});
|
|
786
762
|
}
|
|
787
763
|
|
|
788
764
|
/**
|
package/lib/jwt.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { createSigner, createVerifier, createDecoder } from 'fast-jwt';
|
|
6
6
|
|
|
7
|
+
import type { Algorithm as FastJwtAlgorithm } from 'fast-jwt';
|
|
7
8
|
import type { AuthConfig } from '../types/befly.js';
|
|
8
9
|
import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtHeader } from 'befly-shared/types';
|
|
9
10
|
|
|
@@ -26,15 +27,18 @@ export class Jwt {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
sign(payload: JwtPayload, options: JwtSignOptions = {}): string {
|
|
30
|
+
const key = options.secret || this.config.secret || 'befly-secret';
|
|
31
|
+
const algorithm = (options.algorithm || this.config.algorithm || 'HS256') as FastJwtAlgorithm;
|
|
32
|
+
|
|
29
33
|
const signer = createSigner({
|
|
30
|
-
key:
|
|
31
|
-
algorithm:
|
|
34
|
+
key: key,
|
|
35
|
+
algorithm: algorithm,
|
|
32
36
|
expiresIn: options.expiresIn || this.config.expiresIn,
|
|
33
37
|
iss: options.issuer,
|
|
34
38
|
aud: options.audience,
|
|
35
39
|
sub: options.subject,
|
|
36
40
|
jti: options.jwtId,
|
|
37
|
-
notBefore: options.notBefore
|
|
41
|
+
notBefore: typeof options.notBefore === 'number' ? options.notBefore : undefined
|
|
38
42
|
});
|
|
39
43
|
return signer(payload);
|
|
40
44
|
}
|
|
@@ -43,9 +47,13 @@ export class Jwt {
|
|
|
43
47
|
if (!token || typeof token !== 'string') {
|
|
44
48
|
throw new Error('Token必须是非空字符串');
|
|
45
49
|
}
|
|
50
|
+
const key = options.secret || this.config.secret || 'befly-secret';
|
|
51
|
+
const algorithm = (this.config.algorithm || 'HS256') as FastJwtAlgorithm;
|
|
52
|
+
const algorithms: FastJwtAlgorithm[] = options.algorithms ? (options.algorithms as FastJwtAlgorithm[]) : [algorithm];
|
|
53
|
+
|
|
46
54
|
const verifier = createVerifier({
|
|
47
|
-
key:
|
|
48
|
-
algorithms:
|
|
55
|
+
key: key,
|
|
56
|
+
algorithms: algorithms,
|
|
49
57
|
allowedIss: options.issuer,
|
|
50
58
|
allowedAud: options.audience,
|
|
51
59
|
allowedSub: options.subject,
|
package/lib/redisHelper.ts
CHANGED
|
@@ -140,14 +140,15 @@ export class RedisHelper {
|
|
|
140
140
|
/**
|
|
141
141
|
* 检查键是否存在
|
|
142
142
|
* @param key - 键名
|
|
143
|
+
* @returns 是否存在(true/false)
|
|
143
144
|
*/
|
|
144
|
-
async exists(key: string): Promise<
|
|
145
|
+
async exists(key: string): Promise<boolean> {
|
|
145
146
|
try {
|
|
146
147
|
const pkey = `${this.prefix}${key}`;
|
|
147
148
|
return await this.client.exists(pkey);
|
|
148
149
|
} catch (error: any) {
|
|
149
150
|
Logger.error('Redis exists 错误', error);
|
|
150
|
-
return
|
|
151
|
+
return false;
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
|
|
@@ -221,15 +222,15 @@ export class RedisHelper {
|
|
|
221
222
|
* 判断成员是否在 Set 中
|
|
222
223
|
* @param key - 键名
|
|
223
224
|
* @param member - 成员
|
|
224
|
-
* @returns
|
|
225
|
+
* @returns 是否存在(true/false)
|
|
225
226
|
*/
|
|
226
|
-
async sismember(key: string, member: string): Promise<
|
|
227
|
+
async sismember(key: string, member: string): Promise<boolean> {
|
|
227
228
|
try {
|
|
228
229
|
const pkey = `${this.prefix}${key}`;
|
|
229
230
|
return await this.client.sismember(pkey, member);
|
|
230
231
|
} catch (error: any) {
|
|
231
232
|
Logger.error('Redis sismember 错误', error);
|
|
232
|
-
return
|
|
233
|
+
return false;
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -293,8 +294,7 @@ export class RedisHelper {
|
|
|
293
294
|
}
|
|
294
295
|
|
|
295
296
|
try {
|
|
296
|
-
|
|
297
|
-
return results.map((r) => r > 0);
|
|
297
|
+
return await Promise.all(items.map((item) => this.sismember(item.key, item.member)));
|
|
298
298
|
} catch (error: any) {
|
|
299
299
|
Logger.error('Redis sismemberBatch 错误', error);
|
|
300
300
|
return items.map(() => false);
|
|
@@ -389,8 +389,7 @@ export class RedisHelper {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
try {
|
|
392
|
-
|
|
393
|
-
return results.map((r) => r > 0);
|
|
392
|
+
return await Promise.all(keys.map((key) => this.exists(key)));
|
|
394
393
|
} catch (error: any) {
|
|
395
394
|
Logger.error('Redis existsBatch 错误', error);
|
|
396
395
|
return keys.map(() => false);
|
package/lib/sqlBuilder.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 提供链式 API 构建 SQL 查询
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { WhereConditions, OrderByField } from '../types/common.js';
|
|
6
|
+
import type { WhereConditions, OrderByField, WhereOperator, OrderDirection, SqlQuery, InsertData, UpdateData } from '../types/common.js';
|
|
7
7
|
import type { SqlValue } from 'befly-shared/types';
|
|
8
8
|
|
|
9
9
|
/**
|
package/lib/validator.ts
CHANGED
|
@@ -178,7 +178,8 @@ export class Validator {
|
|
|
178
178
|
private static resolveRegex(regexp: string | null): string | null {
|
|
179
179
|
if (!regexp) return null;
|
|
180
180
|
if (regexp.startsWith('@')) {
|
|
181
|
-
|
|
181
|
+
const key = regexp.substring(1) as keyof typeof RegexAliases;
|
|
182
|
+
return RegexAliases[key] || regexp;
|
|
182
183
|
}
|
|
183
184
|
return regexp;
|
|
184
185
|
}
|
package/loader/loadHooks.ts
CHANGED
|
@@ -19,7 +19,7 @@ export async function loadHooks(hooks: Hook[]): Promise<void> {
|
|
|
19
19
|
|
|
20
20
|
// 2. 过滤禁用的钩子
|
|
21
21
|
const disableHooks = beflyConfig.disableHooks || [];
|
|
22
|
-
const enabledHooks = coreHooks.filter((hook) => !disableHooks.includes(hook.name));
|
|
22
|
+
const enabledHooks = coreHooks.filter((hook) => hook.name && !disableHooks.includes(hook.name));
|
|
23
23
|
|
|
24
24
|
if (disableHooks.length > 0) {
|
|
25
25
|
Logger.info({ hooks: disableHooks }, '禁用钩子');
|
package/loader/loadPlugins.ts
CHANGED
|
@@ -39,7 +39,7 @@ export async function loadPlugins(plugins: Plugin[], context: BeflyContext): Pro
|
|
|
39
39
|
|
|
40
40
|
// 5. 过滤禁用的插件
|
|
41
41
|
const disablePlugins = beflyConfig.disablePlugins || [];
|
|
42
|
-
const enabledPlugins = allPlugins.filter((plugin) => !disablePlugins.includes(plugin.name));
|
|
42
|
+
const enabledPlugins = allPlugins.filter((plugin) => plugin.name && !disablePlugins.includes(plugin.name));
|
|
43
43
|
|
|
44
44
|
if (disablePlugins.length > 0) {
|
|
45
45
|
Logger.info({ plugins: disablePlugins }, '禁用插件');
|
package/main.ts
CHANGED
|
@@ -42,7 +42,7 @@ export class Befly {
|
|
|
42
42
|
private hooks: Hook[] = [];
|
|
43
43
|
|
|
44
44
|
/** 应用上下文 */
|
|
45
|
-
public context: BeflyContext = {};
|
|
45
|
+
public context: Partial<BeflyContext> = {};
|
|
46
46
|
|
|
47
47
|
/** 配置引用(延迟加载) */
|
|
48
48
|
private config: BeflyOptions | null = null;
|
|
@@ -51,7 +51,7 @@ export class Befly {
|
|
|
51
51
|
* 启动完整的生命周期流程
|
|
52
52
|
* @returns HTTP 服务器实例
|
|
53
53
|
*/
|
|
54
|
-
public async start(): Promise<
|
|
54
|
+
public async start(): Promise<ReturnType<typeof Bun.serve>> {
|
|
55
55
|
try {
|
|
56
56
|
const serverStartTime = Bun.nanoseconds();
|
|
57
57
|
|
|
@@ -65,7 +65,7 @@ export class Befly {
|
|
|
65
65
|
await checkApi();
|
|
66
66
|
|
|
67
67
|
// 2. 加载插件
|
|
68
|
-
await loadPlugins(this.plugins, this.context);
|
|
68
|
+
await loadPlugins(this.plugins, this.context as BeflyContext);
|
|
69
69
|
|
|
70
70
|
// 3. 加载钩子
|
|
71
71
|
await loadHooks(this.hooks);
|
|
@@ -84,7 +84,7 @@ export class Befly {
|
|
|
84
84
|
hostname: this.config!.appHost,
|
|
85
85
|
routes: {
|
|
86
86
|
'/': () => Response.json({ code: 0, msg: `${this.config!.appName} 接口服务已启动` }),
|
|
87
|
-
'/api/*': apiHandler(this.apis, this.hooks, this.context),
|
|
87
|
+
'/api/*': apiHandler(this.apis, this.hooks, this.context as BeflyContext),
|
|
88
88
|
'/*': staticHandler()
|
|
89
89
|
},
|
|
90
90
|
error: (error: Error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"bun": ">=1.3.0"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"befly-shared": "^1.
|
|
67
|
+
"befly-shared": "^1.2.0",
|
|
68
68
|
"chalk": "^5.6.2",
|
|
69
69
|
"es-toolkit": "^1.42.0",
|
|
70
70
|
"fast-jwt": "^6.1.0",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"pino": "^10.1.0",
|
|
74
74
|
"pino-roll": "^4.0.0"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "ebb51601e566ac01ef09824309a6ec60a5d32388",
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"typescript": "^5.9.3"
|
|
79
79
|
}
|
package/sync/syncDb/apply.ts
CHANGED
|
@@ -11,9 +11,9 @@ import { isMySQL, isPG, isSQLite, IS_PLAN, CHANGE_TYPE_LABELS, getTypeMapping }
|
|
|
11
11
|
import { logFieldChange, resolveDefaultValue, isStringOrArrayType } from './helpers.js';
|
|
12
12
|
import { executeDDLSafely, buildIndexSQL } from './ddl.js';
|
|
13
13
|
import { rebuildSqliteTable } from './sqlite.js';
|
|
14
|
-
import type { FieldChange, IndexAction, TablePlan, ColumnInfo } from '../../types.js';
|
|
14
|
+
import type { FieldChange, IndexAction, TablePlan, ColumnInfo } from '../../types/sync.js';
|
|
15
15
|
import type { SQL } from 'bun';
|
|
16
|
-
import type { FieldDefinition } from 'befly/types
|
|
16
|
+
import type { FieldDefinition } from 'befly-shared/types';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* 构建 ALTER TABLE SQL 语句
|
package/sync/syncDb/ddl.ts
CHANGED
|
@@ -15,7 +15,8 @@ import { quoteIdentifier, escapeComment } from './helpers.js';
|
|
|
15
15
|
import { resolveDefaultValue, generateDefaultSql, getSqlType } from './types.js';
|
|
16
16
|
|
|
17
17
|
import type { SQL } from 'bun';
|
|
18
|
-
import type { FieldDefinition
|
|
18
|
+
import type { FieldDefinition } from 'befly-shared/types';
|
|
19
|
+
import type { AnyObject } from '../../types/common.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* 构建索引操作 SQL(统一使用在线策略)
|
package/sync/syncDb/schema.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { isMySQL, isPG, isSQLite } from './constants.js';
|
|
11
|
-
import type { ColumnInfo, IndexInfo } from '../../types.js';
|
|
11
|
+
import type { ColumnInfo, IndexInfo } from '../../types/sync.js';
|
|
12
12
|
import type { SQL } from 'bun';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -83,6 +83,7 @@ export async function getTableColumns(sql: SQL, tableName: string, dbName: strin
|
|
|
83
83
|
columns[row.COLUMN_NAME] = {
|
|
84
84
|
type: row.DATA_TYPE,
|
|
85
85
|
columnType: row.COLUMN_TYPE,
|
|
86
|
+
length: row.CHARACTER_MAXIMUM_LENGTH,
|
|
86
87
|
max: row.CHARACTER_MAXIMUM_LENGTH,
|
|
87
88
|
nullable: row.IS_NULLABLE === 'YES',
|
|
88
89
|
defaultValue: defaultValue,
|
|
@@ -111,6 +112,7 @@ export async function getTableColumns(sql: SQL, tableName: string, dbName: strin
|
|
|
111
112
|
columns[row.column_name] = {
|
|
112
113
|
type: row.data_type,
|
|
113
114
|
columnType: row.data_type,
|
|
115
|
+
length: row.character_maximum_length,
|
|
114
116
|
max: row.character_maximum_length,
|
|
115
117
|
nullable: String(row.is_nullable).toUpperCase() === 'YES',
|
|
116
118
|
defaultValue: row.column_default,
|
|
@@ -130,6 +132,7 @@ export async function getTableColumns(sql: SQL, tableName: string, dbName: strin
|
|
|
130
132
|
columns[row.name] = {
|
|
131
133
|
type: baseType.toLowerCase(),
|
|
132
134
|
columnType: baseType.toLowerCase(),
|
|
135
|
+
length: max,
|
|
133
136
|
max: max,
|
|
134
137
|
nullable: row.notnull === 0,
|
|
135
138
|
defaultValue: row.dflt_value,
|
|
@@ -186,7 +189,7 @@ export async function getTableIndexes(sql: SQL, tableName: string, dbName: strin
|
|
|
186
189
|
const list = await sql.unsafe(`PRAGMA index_list(${tableName})`);
|
|
187
190
|
for (const idx of list) {
|
|
188
191
|
const info = await sql.unsafe(`PRAGMA index_info(${idx.name})`);
|
|
189
|
-
const cols = info.map((r) => r.name);
|
|
192
|
+
const cols = info.map((r: any) => r.name);
|
|
190
193
|
if (cols.length === 1) indexes[idx.name] = cols;
|
|
191
194
|
}
|
|
192
195
|
}
|
package/sync/syncDb/sqlite.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Logger } from '../../lib/logger.js';
|
|
|
9
9
|
import { IS_PLAN } from './constants.js';
|
|
10
10
|
import { createTable } from './tableCreate.js';
|
|
11
11
|
import type { SQL } from 'bun';
|
|
12
|
+
import type { FieldDefinition } from 'befly-shared/types';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* SQLite 重建表迁移(简化版)
|
|
@@ -25,10 +26,10 @@ import type { SQL } from 'bun';
|
|
|
25
26
|
* @param tableName - 表名
|
|
26
27
|
* @param fields - 字段定义对象
|
|
27
28
|
*/
|
|
28
|
-
export async function rebuildSqliteTable(sql: SQL, tableName: string, fields: Record<string,
|
|
29
|
+
export async function rebuildSqliteTable(sql: SQL, tableName: string, fields: Record<string, FieldDefinition>): Promise<void> {
|
|
29
30
|
// 1. 读取现有列顺序
|
|
30
31
|
const info = await sql.unsafe(`PRAGMA table_info(${tableName})`);
|
|
31
|
-
const existingCols = info.map((r) => r.name);
|
|
32
|
+
const existingCols = info.map((r: any) => r.name);
|
|
32
33
|
const targetCols = ['id', 'created_at', 'updated_at', 'deleted_at', 'state', ...Object.keys(fields)];
|
|
33
34
|
const tmpTable = `${tableName}__tmp__${Date.now()}`;
|
|
34
35
|
|
package/sync/syncDb/table.ts
CHANGED
|
@@ -14,9 +14,9 @@ import { logFieldChange, resolveDefaultValue, generateDefaultSql, isStringOrArra
|
|
|
14
14
|
import { generateDDLClause, isPgCompatibleTypeChange } from './ddl.js';
|
|
15
15
|
import { getTableColumns, getTableIndexes } from './schema.js';
|
|
16
16
|
import { compareFieldDefinition, applyTablePlan } from './apply.js';
|
|
17
|
-
import type { TablePlan } from '../../types.js';
|
|
17
|
+
import type { TablePlan } from '../../types/sync.js';
|
|
18
18
|
import type { SQL } from 'bun';
|
|
19
|
-
import type { FieldDefinition } from 'befly/types
|
|
19
|
+
import type { FieldDefinition } from 'befly-shared/types';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 同步表结构(对比和应用变更)
|
|
@@ -34,30 +34,30 @@ import type { FieldDefinition } from 'befly/types/common';
|
|
|
34
34
|
* @param dbName - 数据库名称
|
|
35
35
|
*/
|
|
36
36
|
export async function modifyTable(sql: SQL, tableName: string, fields: Record<string, FieldDefinition>, force: boolean = false, dbName?: string): Promise<TablePlan> {
|
|
37
|
-
const existingColumns = await getTableColumns(sql, tableName, dbName);
|
|
38
|
-
const existingIndexes = await getTableIndexes(sql, tableName, dbName);
|
|
37
|
+
const existingColumns = await getTableColumns(sql, tableName, dbName || '');
|
|
38
|
+
const existingIndexes = await getTableIndexes(sql, tableName, dbName || '');
|
|
39
39
|
let changed = false;
|
|
40
40
|
|
|
41
|
-
const addClauses = [];
|
|
42
|
-
const modifyClauses = [];
|
|
43
|
-
const defaultClauses = [];
|
|
44
|
-
const indexActions = [];
|
|
41
|
+
const addClauses: string[] = [];
|
|
42
|
+
const modifyClauses: string[] = [];
|
|
43
|
+
const defaultClauses: string[] = [];
|
|
44
|
+
const indexActions: Array<{ action: 'create' | 'drop'; indexName: string; fieldName: string }> = [];
|
|
45
45
|
|
|
46
46
|
for (const [fieldKey, fieldDef] of Object.entries(fields)) {
|
|
47
47
|
// 转换字段名为下划线格式
|
|
48
48
|
const dbFieldName = snakeCase(fieldKey);
|
|
49
49
|
|
|
50
50
|
if (existingColumns[dbFieldName]) {
|
|
51
|
-
const comparison = compareFieldDefinition(existingColumns[dbFieldName], fieldDef
|
|
51
|
+
const comparison = compareFieldDefinition(existingColumns[dbFieldName], fieldDef);
|
|
52
52
|
if (comparison.length > 0) {
|
|
53
53
|
for (const c of comparison) {
|
|
54
54
|
// 使用统一的日志格式函数和常量标签
|
|
55
|
-
const changeLabel = CHANGE_TYPE_LABELS[c.type] || '未知';
|
|
55
|
+
const changeLabel = CHANGE_TYPE_LABELS[c.type as keyof typeof CHANGE_TYPE_LABELS] || '未知';
|
|
56
56
|
logFieldChange(tableName, dbFieldName, c.type, c.current, c.expected, changeLabel);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
if (isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max) {
|
|
60
|
-
if (existingColumns[dbFieldName].max > fieldDef.max) {
|
|
59
|
+
if (isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && fieldDef.max !== null) {
|
|
60
|
+
if (existingColumns[dbFieldName].max! > fieldDef.max) {
|
|
61
61
|
if (force) {
|
|
62
62
|
Logger.warn(`[强制执行] ${tableName}.${dbFieldName} 长度收缩 ${existingColumns[dbFieldName].max} -> ${fieldDef.max}`);
|
|
63
63
|
} else {
|
|
@@ -106,8 +106,8 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
|
|
|
106
106
|
// 若不仅仅是默认值变化,继续生成修改子句
|
|
107
107
|
if (!onlyDefaultChanged) {
|
|
108
108
|
let skipModify = false;
|
|
109
|
-
if (hasLengthChange && isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max) {
|
|
110
|
-
const isShrink = existingColumns[dbFieldName].max > fieldDef.max;
|
|
109
|
+
if (hasLengthChange && isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && fieldDef.max !== null) {
|
|
110
|
+
const isShrink = existingColumns[dbFieldName].max! > fieldDef.max;
|
|
111
111
|
if (isShrink && !force) skipModify = true;
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -16,7 +16,7 @@ import { buildSystemColumnDefs, buildBusinessColumnDefs, buildIndexSQL } from '.
|
|
|
16
16
|
import { getTableIndexes } from './schema.js';
|
|
17
17
|
|
|
18
18
|
import type { SQL } from 'bun';
|
|
19
|
-
import type { FieldDefinition } from 'befly/types
|
|
19
|
+
import type { FieldDefinition } from 'befly-shared/types';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 为 PostgreSQL 表添加列注释
|
|
@@ -74,7 +74,7 @@ async function createTableIndexes(sql: SQL, tableName: string, fields: Record<st
|
|
|
74
74
|
// 获取现有索引(MySQL 不支持 IF NOT EXISTS,需要先检查)
|
|
75
75
|
let existingIndexes: Record<string, string[]> = {};
|
|
76
76
|
if (isMySQL()) {
|
|
77
|
-
existingIndexes = await getTableIndexes(sql, tableName, dbName);
|
|
77
|
+
existingIndexes = await getTableIndexes(sql, tableName, dbName || '');
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// 系统字段索引
|
package/sync/syncDb/types.ts
CHANGED
|
@@ -70,7 +70,7 @@ export function getSqlType(fieldType: string, fieldMax: number | null, unsigned:
|
|
|
70
70
|
* resolveDefaultValue('admin', 'string') // => 'admin'
|
|
71
71
|
* resolveDefaultValue(0, 'number') // => 0
|
|
72
72
|
*/
|
|
73
|
-
export function resolveDefaultValue(fieldDefault: any, fieldType:
|
|
73
|
+
export function resolveDefaultValue(fieldDefault: any, fieldType: string): any {
|
|
74
74
|
// null 或字符串 'null' 都表示使用类型默认值
|
|
75
75
|
if (fieldDefault !== null && fieldDefault !== 'null') {
|
|
76
76
|
return fieldDefault;
|
|
@@ -83,6 +83,8 @@ export function resolveDefaultValue(fieldDefault: any, fieldType: 'number' | 'st
|
|
|
83
83
|
case 'string':
|
|
84
84
|
return '';
|
|
85
85
|
case 'array':
|
|
86
|
+
case 'array_string':
|
|
87
|
+
case 'array_text':
|
|
86
88
|
return '[]';
|
|
87
89
|
case 'text':
|
|
88
90
|
// text 类型不设置默认值,保持 'null'
|
|
@@ -104,15 +106,16 @@ export function resolveDefaultValue(fieldDefault: any, fieldType: 'number' | 'st
|
|
|
104
106
|
* generateDefaultSql('admin', 'string') // => " DEFAULT 'admin'"
|
|
105
107
|
* generateDefaultSql('', 'string') // => " DEFAULT ''"
|
|
106
108
|
* generateDefaultSql('null', 'text') // => ''
|
|
109
|
+
* generateDefaultSql('[]', 'array_text') // => '' (TEXT 类型不能有默认值)
|
|
107
110
|
*/
|
|
108
|
-
export function generateDefaultSql(actualDefault: any, fieldType:
|
|
109
|
-
// text
|
|
110
|
-
if (fieldType === 'text' || actualDefault === 'null') {
|
|
111
|
+
export function generateDefaultSql(actualDefault: any, fieldType: string): string {
|
|
112
|
+
// text 和 array_text 类型不设置默认值(MySQL TEXT 类型不支持默认值)
|
|
113
|
+
if (fieldType === 'text' || fieldType === 'array_text' || actualDefault === 'null') {
|
|
111
114
|
return '';
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
// 仅 number/string/
|
|
115
|
-
if (fieldType === 'number' || fieldType === 'string' || fieldType === '
|
|
117
|
+
// 仅 number/string/array_string 类型设置默认值
|
|
118
|
+
if (fieldType === 'number' || fieldType === 'string' || fieldType === 'array_string') {
|
|
116
119
|
if (typeof actualDefault === 'number' && !Number.isNaN(actualDefault)) {
|
|
117
120
|
return ` DEFAULT ${actualDefault}`;
|
|
118
121
|
} else {
|
package/sync/syncDb.ts
CHANGED
|
@@ -117,7 +117,7 @@ export async function syncDbCommand(options: SyncDbOptions = {}): Promise<void>
|
|
|
117
117
|
applyFieldDefaults(fieldDef);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const dbName = beflyConfig.db?.database;
|
|
120
|
+
const dbName = beflyConfig.db?.database || '';
|
|
121
121
|
const existsTable = await tableExists(sql!, tableName, dbName);
|
|
122
122
|
|
|
123
123
|
// 读取 force 参数
|
package/sync/syncDev.ts
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
* - 表名: addon_admin_admin
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { scanAddons, getAddonDir, normalizeModuleForSync } from 'befly-shared/addonHelper';
|
|
11
|
-
|
|
12
10
|
import { Logger } from '../lib/logger.js';
|
|
13
11
|
import { Cipher } from '../lib/cipher.js';
|
|
14
12
|
import { Connect } from '../lib/connect.js';
|
|
@@ -17,7 +15,7 @@ import { RedisHelper } from '../lib/redisHelper.js';
|
|
|
17
15
|
import { CacheHelper } from '../lib/cacheHelper.js';
|
|
18
16
|
import { beflyConfig } from '../befly.config.js';
|
|
19
17
|
|
|
20
|
-
import type { SyncDevOptions
|
|
18
|
+
import type { SyncDevOptions } from '../types/index.js';
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* SyncDev 命令主函数
|
package/sync/syncMenu.ts
CHANGED
|
@@ -243,7 +243,7 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<void> {
|
|
|
243
243
|
|
|
244
244
|
for (const menu of menus) {
|
|
245
245
|
try {
|
|
246
|
-
await syncMenuRecursive(helper, menu, 0, existingMenuMap
|
|
246
|
+
await syncMenuRecursive(helper, menu, 0, existingMenuMap);
|
|
247
247
|
} catch (error: any) {
|
|
248
248
|
Logger.error({ err: error, menu: menu.name }, '同步菜单失败');
|
|
249
249
|
throw error;
|
|
@@ -261,7 +261,7 @@ describe('CacheHelper', () => {
|
|
|
261
261
|
|
|
262
262
|
describe('checkRolePermission', () => {
|
|
263
263
|
it('有权限时返回 true', async () => {
|
|
264
|
-
mockRedis.sismember = mock(() => Promise.resolve(
|
|
264
|
+
mockRedis.sismember = mock(() => Promise.resolve(true));
|
|
265
265
|
|
|
266
266
|
const result = await cacheHelper.checkRolePermission('admin', 'POST/api/login');
|
|
267
267
|
|
|
@@ -270,7 +270,7 @@ describe('CacheHelper', () => {
|
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
it('无权限时返回 false', async () => {
|
|
273
|
-
mockRedis.sismember = mock(() => Promise.resolve(
|
|
273
|
+
mockRedis.sismember = mock(() => Promise.resolve(false));
|
|
274
274
|
|
|
275
275
|
const result = await cacheHelper.checkRolePermission('user', 'POST/api/admin/del');
|
|
276
276
|
|
package/tests/cipher.test.ts
CHANGED
|
@@ -219,14 +219,14 @@ describe('Cipher - 快速哈希', () => {
|
|
|
219
219
|
|
|
220
220
|
test('快速哈希 - 字符串输入', () => {
|
|
221
221
|
const result = Cipher.fastHash(testData);
|
|
222
|
-
expect(typeof result).toBe('
|
|
223
|
-
expect(result).toBeGreaterThan(
|
|
222
|
+
expect(typeof result).toBe('number');
|
|
223
|
+
expect(result).toBeGreaterThan(0);
|
|
224
224
|
});
|
|
225
225
|
|
|
226
226
|
test('快速哈希 - Uint8Array 输入', () => {
|
|
227
227
|
const bytes = new TextEncoder().encode(testData);
|
|
228
228
|
const result = Cipher.fastHash(bytes);
|
|
229
|
-
expect(typeof result).toBe('
|
|
229
|
+
expect(typeof result).toBe('number');
|
|
230
230
|
});
|
|
231
231
|
|
|
232
232
|
test('快速哈希 - 相同输入产生相同哈希', () => {
|
|
@@ -243,6 +243,6 @@ describe('Cipher - 快速哈希', () => {
|
|
|
243
243
|
|
|
244
244
|
test('快速哈希 - 空字符串', () => {
|
|
245
245
|
const result = Cipher.fastHash('');
|
|
246
|
-
expect(typeof result).toBe('
|
|
246
|
+
expect(typeof result).toBe('number');
|
|
247
247
|
});
|
|
248
248
|
});
|
|
@@ -93,8 +93,12 @@ describe('generateDefaultSql', () => {
|
|
|
93
93
|
expect(generateDefaultSql('null', 'text')).toBe('');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
test('
|
|
97
|
-
expect(generateDefaultSql('[]', '
|
|
96
|
+
test('array_string 类型生成 JSON 数组默认值', () => {
|
|
97
|
+
expect(generateDefaultSql('[]', 'array_string')).toBe(" DEFAULT '[]'");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('array_text 类型不生成默认值(MySQL TEXT 不支持)', () => {
|
|
101
|
+
expect(generateDefaultSql('[]', 'array_text')).toBe('');
|
|
98
102
|
});
|
|
99
103
|
|
|
100
104
|
test('单引号被正确转义', () => {
|
package/tsconfig.json
CHANGED
|
@@ -46,11 +46,10 @@
|
|
|
46
46
|
"types": ["bun"],
|
|
47
47
|
|
|
48
48
|
// 路径
|
|
49
|
-
"baseUrl": ".",
|
|
50
49
|
"paths": {
|
|
51
50
|
"@/*": ["./*"]
|
|
52
51
|
}
|
|
53
52
|
},
|
|
54
53
|
"include": ["**/*.ts", "**/*.js"],
|
|
55
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.js"]
|
|
54
|
+
"exclude": ["node_modules", "dist", "logs", "temp", "main.single.ts", "**/*.test.ts", "**/*.test.js"]
|
|
56
55
|
}
|
package/types/befly.d.ts
CHANGED
|
@@ -142,31 +142,31 @@ export interface BeflyOptions {
|
|
|
142
142
|
export interface BeflyContext extends KeyValue {
|
|
143
143
|
// ========== 核心插件 ==========
|
|
144
144
|
/** 数据库助手 */
|
|
145
|
-
db
|
|
145
|
+
db: DbHelper;
|
|
146
146
|
|
|
147
147
|
/** Redis 助手 */
|
|
148
|
-
redis
|
|
148
|
+
redis: RedisHelper;
|
|
149
149
|
|
|
150
150
|
/** 日志器 */
|
|
151
|
-
logger
|
|
151
|
+
logger: typeof Logger;
|
|
152
152
|
|
|
153
153
|
/** 缓存助手 */
|
|
154
|
-
cache
|
|
154
|
+
cache: CacheHelper;
|
|
155
155
|
|
|
156
156
|
/** 工具函数 */
|
|
157
|
-
tool
|
|
157
|
+
tool: {
|
|
158
158
|
Yes: (msg: string, data?: any, other?: Record<string, any>) => { code: 0; msg: string; data: any };
|
|
159
159
|
No: (msg: string, data?: any, other?: Record<string, any>) => { code: 1; msg: string; data: any };
|
|
160
160
|
};
|
|
161
161
|
|
|
162
162
|
/** 加密解密 */
|
|
163
|
-
cipher
|
|
163
|
+
cipher: typeof Cipher;
|
|
164
164
|
|
|
165
165
|
/** JWT 令牌 */
|
|
166
|
-
jwt
|
|
166
|
+
jwt: Jwt;
|
|
167
167
|
|
|
168
168
|
/** 项目配置 */
|
|
169
|
-
config
|
|
169
|
+
config: BeflyOptions;
|
|
170
170
|
|
|
171
171
|
// ========== 动态插件 ==========
|
|
172
172
|
/** 组件插件:addon_{addonName}_{pluginName} */
|
package/types/common.d.ts
CHANGED
|
@@ -3,6 +3,55 @@
|
|
|
3
3
|
* Core 专用类型,通用类型请直接从 befly-shared/types 导入
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { SqlValue } from 'befly-shared/types';
|
|
7
|
+
|
|
8
|
+
// ============================================
|
|
9
|
+
// SQL 查询相关类型
|
|
10
|
+
// ============================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* WHERE 条件操作符
|
|
14
|
+
*/
|
|
15
|
+
export type WhereOperator = '$eq' | '$ne' | '$not' | '$gt' | '$gte' | '$lt' | '$lte' | '$like' | '$notLike' | '$in' | '$notIn' | '$nin' | '$isNull' | '$isNotNull' | '$null' | '$notNull' | '$between' | '$notBetween';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* WHERE 条件类型
|
|
19
|
+
*/
|
|
20
|
+
export type WhereConditions = Record<string, any>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 排序方向
|
|
24
|
+
*/
|
|
25
|
+
export type OrderDirection = 'ASC' | 'DESC';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 排序字段
|
|
29
|
+
*/
|
|
30
|
+
export type OrderByField = string | { field: string; direction: OrderDirection };
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* SQL 查询结果
|
|
34
|
+
*/
|
|
35
|
+
export interface SqlQuery {
|
|
36
|
+
sql: string;
|
|
37
|
+
params: SqlValue[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 插入数据类型
|
|
42
|
+
*/
|
|
43
|
+
export type InsertData = Record<string, SqlValue> | Record<string, SqlValue>[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 更新数据类型
|
|
47
|
+
*/
|
|
48
|
+
export type UpdateData = Record<string, SqlValue>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 任意对象类型
|
|
52
|
+
*/
|
|
53
|
+
export type AnyObject = Record<string, any>;
|
|
54
|
+
|
|
6
55
|
// ============================================
|
|
7
56
|
// Core 专用类型(不适合放在 shared 中的类型)
|
|
8
57
|
// ============================================
|
package/types/database.d.ts
CHANGED
|
@@ -3,15 +3,35 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { SqlValue } from 'befly-shared/types';
|
|
6
|
-
import type { WhereConditions } from './common';
|
|
7
6
|
import type { DatabaseTables, TableName, TableType, TableInsertType, TableUpdateType, TypedWhereConditions } from './table';
|
|
8
7
|
|
|
9
|
-
// 重新导出 WhereOperator 和 WhereConditions,供其他模块使用
|
|
10
|
-
export type { WhereOperator, WhereConditions } from './index';
|
|
11
|
-
|
|
12
8
|
// 重新导出表类型工具
|
|
13
9
|
export type { DatabaseTables, TableName, TableType, TableInsertType, TableUpdateType, SystemFields, BaseTable, InsertType, UpdateType, SelectType, TypedWhereConditions } from './table';
|
|
14
10
|
|
|
11
|
+
// ============================================
|
|
12
|
+
// SQL 查询相关类型
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* WHERE 条件操作符
|
|
17
|
+
*/
|
|
18
|
+
export type WhereOperator = '$eq' | '$ne' | '$not' | '$gt' | '$gte' | '$lt' | '$lte' | '$like' | '$notLike' | '$in' | '$notIn' | '$nin' | '$isNull' | '$isNotNull' | '$null' | '$notNull' | '$between' | '$notBetween';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* WHERE 条件类型
|
|
22
|
+
*/
|
|
23
|
+
export type WhereConditions = Record<string, any>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 排序方向
|
|
27
|
+
*/
|
|
28
|
+
export type OrderDirection = 'ASC' | 'DESC';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 排序字段
|
|
32
|
+
*/
|
|
33
|
+
export type OrderByField = string | { field: string; direction: OrderDirection };
|
|
34
|
+
|
|
15
35
|
// ============================================
|
|
16
36
|
// 泛型查询选项(类型安全版本)
|
|
17
37
|
// ============================================
|
package/types/index.ts
CHANGED
|
@@ -8,12 +8,17 @@
|
|
|
8
8
|
export * from './api.js';
|
|
9
9
|
export * from './befly.js';
|
|
10
10
|
export * from './cache.js';
|
|
11
|
-
export * from './common.js';
|
|
12
11
|
export * from './context.js';
|
|
13
|
-
export * from './database.js';
|
|
14
12
|
export * from './hook.js';
|
|
15
13
|
export * from './plugin.js';
|
|
16
14
|
export * from './redis.js';
|
|
17
15
|
export * from './table.js';
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
|
|
17
|
+
// common.js 包含基础类型
|
|
18
|
+
export * from './common.js';
|
|
19
|
+
|
|
20
|
+
// database.js 中除了与 common.js 重复的类型外,其他都导出
|
|
21
|
+
export type { TypedQueryOptions, TypedInsertOptions, TypedUpdateOptions, TypedDeleteOptions, QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, TransactionCallback, DbType, ColumnInfo, IndexInfo } from './database.js';
|
|
22
|
+
|
|
23
|
+
// sync.js 中不重复的类型
|
|
24
|
+
export type { SyncDbOptions, SyncOptions, SyncMenuOptions, MenuConfig, SyncDevOptions, SyncApiOptions, ApiInfo, SyncMenuStats, SyncApiStats, GlobalStats, PhaseStats, SyncReport, SyncReportMeta, DatabaseReport, ApiReport, MenuReport } from './sync.js';
|
package/types/plugin.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export type PluginInitFunction = (befly: BeflyContext) => Promise<any> | any;
|
|
|
15
15
|
*/
|
|
16
16
|
export type Next = () => Promise<void>;
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* 插件请求钩子类型
|
|
20
|
+
*/
|
|
21
|
+
export type PluginRequestHook = (ctx: RequestContext, next: Next) => Promise<void> | void;
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* 插件配置类型
|
|
20
25
|
*/
|
package/types/sync.d.ts
CHANGED
|
@@ -101,6 +101,7 @@ export interface ColumnInfo {
|
|
|
101
101
|
type: string;
|
|
102
102
|
columnType: string;
|
|
103
103
|
length: number | null;
|
|
104
|
+
max?: number | null;
|
|
104
105
|
nullable: boolean;
|
|
105
106
|
defaultValue: any;
|
|
106
107
|
comment: string | null;
|
|
@@ -117,7 +118,7 @@ export interface IndexInfo {
|
|
|
117
118
|
* 字段变更接口
|
|
118
119
|
*/
|
|
119
120
|
export interface FieldChange {
|
|
120
|
-
type: 'length' | 'datatype' | 'comment' | 'default';
|
|
121
|
+
type: 'length' | 'datatype' | 'comment' | 'default' | 'nullable';
|
|
121
122
|
current: any;
|
|
122
123
|
expected: any;
|
|
123
124
|
}
|