befly 2.3.3 → 3.0.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.
Files changed (93) hide show
  1. package/apis/health/info.ts +64 -0
  2. package/apis/tool/tokenCheck.ts +51 -0
  3. package/bin/befly.ts +202 -0
  4. package/checks/conflict.ts +408 -0
  5. package/checks/{table.js → table.ts} +139 -61
  6. package/config/env.ts +218 -0
  7. package/config/reserved.ts +96 -0
  8. package/main.ts +101 -0
  9. package/package.json +44 -8
  10. package/plugins/{db.js → db.ts} +24 -11
  11. package/plugins/logger.ts +28 -0
  12. package/plugins/redis.ts +51 -0
  13. package/plugins/tool.ts +34 -0
  14. package/scripts/syncDb/apply.ts +171 -0
  15. package/scripts/syncDb/constants.ts +70 -0
  16. package/scripts/syncDb/ddl.ts +182 -0
  17. package/scripts/syncDb/helpers.ts +172 -0
  18. package/scripts/syncDb/index.ts +215 -0
  19. package/scripts/syncDb/schema.ts +199 -0
  20. package/scripts/syncDb/sqlite.ts +50 -0
  21. package/scripts/syncDb/state.ts +104 -0
  22. package/scripts/syncDb/table.ts +204 -0
  23. package/scripts/syncDb/tableCreate.ts +142 -0
  24. package/scripts/syncDb/tests/constants.test.ts +104 -0
  25. package/scripts/syncDb/tests/ddl.test.ts +134 -0
  26. package/scripts/syncDb/tests/helpers.test.ts +70 -0
  27. package/scripts/syncDb/types.ts +92 -0
  28. package/scripts/syncDb/version.ts +73 -0
  29. package/scripts/syncDb.ts +9 -0
  30. package/scripts/{syncDev.js → syncDev.ts} +41 -25
  31. package/system.ts +149 -0
  32. package/tables/_common.json +21 -0
  33. package/tables/admin.json +10 -0
  34. package/tsconfig.json +58 -0
  35. package/types/api.d.ts +246 -0
  36. package/types/befly.d.ts +234 -0
  37. package/types/common.d.ts +215 -0
  38. package/types/context.ts +167 -0
  39. package/types/crypto.d.ts +23 -0
  40. package/types/database.d.ts +278 -0
  41. package/types/index.d.ts +16 -0
  42. package/types/index.ts +459 -0
  43. package/types/jwt.d.ts +99 -0
  44. package/types/logger.d.ts +43 -0
  45. package/types/plugin.d.ts +109 -0
  46. package/types/redis.d.ts +44 -0
  47. package/types/tool.d.ts +67 -0
  48. package/types/validator.d.ts +45 -0
  49. package/utils/addonHelper.ts +60 -0
  50. package/utils/api.ts +23 -0
  51. package/utils/{colors.js → colors.ts} +79 -21
  52. package/utils/crypto.ts +308 -0
  53. package/utils/datetime.ts +51 -0
  54. package/utils/dbHelper.ts +142 -0
  55. package/utils/errorHandler.ts +68 -0
  56. package/utils/index.ts +46 -0
  57. package/utils/jwt.ts +493 -0
  58. package/utils/logger.ts +284 -0
  59. package/utils/objectHelper.ts +68 -0
  60. package/utils/pluginHelper.ts +62 -0
  61. package/utils/redisHelper.ts +338 -0
  62. package/utils/response.ts +38 -0
  63. package/utils/{sqlBuilder.js → sqlBuilder.ts} +233 -97
  64. package/utils/sqlHelper.ts +447 -0
  65. package/utils/tableHelper.ts +167 -0
  66. package/utils/tool.ts +230 -0
  67. package/utils/typeHelper.ts +101 -0
  68. package/utils/validate.ts +451 -0
  69. package/utils/{xml.js → xml.ts} +100 -74
  70. package/.npmrc +0 -3
  71. package/.prettierignore +0 -2
  72. package/.prettierrc +0 -11
  73. package/apis/health/info.js +0 -49
  74. package/apis/tool/tokenCheck.js +0 -29
  75. package/bin/befly.js +0 -109
  76. package/config/env.js +0 -64
  77. package/main.js +0 -579
  78. package/plugins/logger.js +0 -14
  79. package/plugins/redis.js +0 -32
  80. package/plugins/tool.js +0 -8
  81. package/scripts/syncDb.js +0 -752
  82. package/system.js +0 -118
  83. package/tables/common.json +0 -16
  84. package/tables/tool.json +0 -6
  85. package/utils/api.js +0 -27
  86. package/utils/crypto.js +0 -260
  87. package/utils/index.js +0 -334
  88. package/utils/jwt.js +0 -387
  89. package/utils/logger.js +0 -143
  90. package/utils/redisHelper.js +0 -74
  91. package/utils/sqlManager.js +0 -471
  92. package/utils/tool.js +0 -31
  93. package/utils/validate.js +0 -226
@@ -0,0 +1,142 @@
1
+ /**
2
+ * syncDb 表创建模块
3
+ *
4
+ * 包含:
5
+ * - 创建表(包含系统字段和业务字段)
6
+ * - 添加 PostgreSQL 注释
7
+ * - 创建表索引
8
+ *
9
+ * 注意:此模块从 table.ts 中提取,用于解除循环依赖
10
+ */
11
+
12
+ import { Logger } from '../../utils/logger.js';
13
+ import { IS_MYSQL, IS_PG, MYSQL_TABLE_CONFIG } from './constants.js';
14
+ import { quoteIdentifier } from './helpers.js';
15
+ import { buildSystemColumnDefs, buildBusinessColumnDefs, buildIndexSQL } from './ddl.js';
16
+ import { parseRule } from '../../utils/tableHelper.js';
17
+ import type { SQL } from 'bun';
18
+
19
+ // 是否为计划模式(从环境变量读取)
20
+ const IS_PLAN = process.argv.includes('--plan');
21
+
22
+ /**
23
+ * 为 PostgreSQL 表添加列注释
24
+ *
25
+ * @param sql - SQL 客户端实例
26
+ * @param tableName - 表名
27
+ * @param fields - 字段定义对象
28
+ */
29
+ async function addPostgresComments(sql: SQL, tableName: string, fields: Record<string, string>): Promise<void> {
30
+ // 系统字段注释
31
+ const systemComments = [
32
+ ['id', '主键ID'],
33
+ ['created_at', '创建时间'],
34
+ ['updated_at', '更新时间'],
35
+ ['deleted_at', '删除时间'],
36
+ ['state', '状态字段']
37
+ ];
38
+
39
+ for (const [name, comment] of systemComments) {
40
+ const stmt = `COMMENT ON COLUMN "${tableName}"."${name}" IS '${comment}'`;
41
+ if (IS_PLAN) {
42
+ Logger.info(`[计划] ${stmt}`);
43
+ } else {
44
+ await sql.unsafe(stmt);
45
+ }
46
+ }
47
+
48
+ // 业务字段注释
49
+ for (const [fieldKey, fieldRule] of Object.entries(fields)) {
50
+ const parsed = parseRule(fieldRule);
51
+ const { name: fieldName } = parsed;
52
+ const stmt = `COMMENT ON COLUMN "${tableName}"."${fieldKey}" IS '${fieldName}'`;
53
+ if (IS_PLAN) {
54
+ Logger.info(`[计划] ${stmt}`);
55
+ } else {
56
+ await sql.unsafe(stmt);
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 创建表的索引(并行执行以提升性能)
63
+ *
64
+ * @param sql - SQL 客户端实例
65
+ * @param tableName - 表名
66
+ * @param fields - 字段定义对象
67
+ * @param systemIndexFields - 系统字段索引列表
68
+ */
69
+ async function createTableIndexes(sql: SQL, tableName: string, fields: Record<string, string>, systemIndexFields: string[]): Promise<void> {
70
+ const indexTasks: Promise<void>[] = [];
71
+
72
+ // 系统字段索引
73
+ for (const sysField of systemIndexFields) {
74
+ const stmt = buildIndexSQL(tableName, `idx_${sysField}`, sysField, 'create');
75
+ if (IS_PLAN) {
76
+ Logger.info(`[计划] ${stmt}`);
77
+ } else {
78
+ indexTasks.push(sql.unsafe(stmt));
79
+ }
80
+ }
81
+
82
+ // 业务字段索引
83
+ for (const [fieldKey, fieldRule] of Object.entries(fields)) {
84
+ const parsed = parseRule(fieldRule);
85
+ if (parsed.index === 1) {
86
+ const stmt = buildIndexSQL(tableName, `idx_${fieldKey}`, fieldKey, 'create');
87
+ if (IS_PLAN) {
88
+ Logger.info(`[计划] ${stmt}`);
89
+ } else {
90
+ indexTasks.push(sql.unsafe(stmt));
91
+ }
92
+ }
93
+ }
94
+
95
+ // 并行执行所有索引创建
96
+ if (indexTasks.length > 0) {
97
+ await Promise.all(indexTasks);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 创建表(包含系统字段和业务字段)
103
+ *
104
+ * @param sql - SQL 客户端实例
105
+ * @param tableName - 表名
106
+ * @param fields - 字段定义对象
107
+ * @param systemIndexFields - 系统字段索引列表(可选,默认使用 ['created_at', 'updated_at', 'state'])
108
+ */
109
+ export async function createTable(sql: SQL, tableName: string, fields: Record<string, string>, systemIndexFields: string[] = ['created_at', 'updated_at', 'state']): Promise<void> {
110
+ // 构建列定义
111
+ const colDefs = [...buildSystemColumnDefs(), ...buildBusinessColumnDefs(fields)];
112
+
113
+ // 生成 CREATE TABLE 语句
114
+ const cols = colDefs.join(',\n ');
115
+ const tableQuoted = quoteIdentifier(tableName);
116
+ const { ENGINE, CHARSET, COLLATE } = MYSQL_TABLE_CONFIG;
117
+ const createSQL = IS_MYSQL
118
+ ? `CREATE TABLE ${tableQuoted} (
119
+ ${cols}
120
+ ) ENGINE=${ENGINE} DEFAULT CHARSET=${CHARSET} COLLATE=${COLLATE}`
121
+ : `CREATE TABLE ${tableQuoted} (
122
+ ${cols}
123
+ )`;
124
+
125
+ if (IS_PLAN) {
126
+ Logger.info(`[计划] ${createSQL.replace(/\n+/g, ' ')}`);
127
+ } else {
128
+ await sql.unsafe(createSQL);
129
+ Logger.info(`[新建表] ${tableName}`);
130
+ }
131
+
132
+ // PostgreSQL: 添加列注释
133
+ if (IS_PG && !IS_PLAN) {
134
+ await addPostgresComments(sql, tableName, fields);
135
+ } else if (IS_PG && IS_PLAN) {
136
+ // 计划模式也要输出注释语句
137
+ await addPostgresComments(sql, tableName, fields);
138
+ }
139
+
140
+ // 创建索引
141
+ await createTableIndexes(sql, tableName, fields, systemIndexFields);
142
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * syncDb 常量模块测试
3
+ */
4
+
5
+ import { describe, test, expect } from 'bun:test';
6
+ import { DB_VERSION_REQUIREMENTS, SYSTEM_FIELDS, SYSTEM_INDEX_FIELDS, CHANGE_TYPE_LABELS, MYSQL_TABLE_CONFIG, IS_MYSQL, IS_PG, IS_SQLITE, typeMapping } from '../constants.js';
7
+
8
+ describe('syncDb/constants', () => {
9
+ describe('数据库版本要求', () => {
10
+ test('应定义 MySQL 最低版本', () => {
11
+ expect(DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR).toBe(8);
12
+ });
13
+
14
+ test('应定义 PostgreSQL 最低版本', () => {
15
+ expect(DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR).toBe(17);
16
+ });
17
+
18
+ test('应定义 SQLite 最低版本', () => {
19
+ expect(DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION).toBe('3.50.0');
20
+ expect(DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION_NUM).toBe(35000);
21
+ });
22
+ });
23
+
24
+ describe('系统字段定义', () => {
25
+ test('应包含 5 个系统字段', () => {
26
+ const fields = Object.keys(SYSTEM_FIELDS);
27
+ expect(fields).toHaveLength(5);
28
+ expect(fields).toContain('ID');
29
+ expect(fields).toContain('CREATED_AT');
30
+ expect(fields).toContain('UPDATED_AT');
31
+ expect(fields).toContain('DELETED_AT');
32
+ expect(fields).toContain('STATE');
33
+ });
34
+
35
+ test('每个系统字段应有名称和注释', () => {
36
+ expect(SYSTEM_FIELDS.ID.name).toBe('id');
37
+ expect(SYSTEM_FIELDS.ID.comment).toBe('主键ID');
38
+ });
39
+ });
40
+
41
+ describe('系统索引字段', () => {
42
+ test('应包含 3 个索引字段', () => {
43
+ expect(SYSTEM_INDEX_FIELDS).toHaveLength(3);
44
+ expect(SYSTEM_INDEX_FIELDS).toContain('created_at');
45
+ expect(SYSTEM_INDEX_FIELDS).toContain('updated_at');
46
+ expect(SYSTEM_INDEX_FIELDS).toContain('state');
47
+ });
48
+ });
49
+
50
+ describe('变更类型标签', () => {
51
+ test('应包含所有变更类型', () => {
52
+ expect(CHANGE_TYPE_LABELS.length).toBe('长度');
53
+ expect(CHANGE_TYPE_LABELS.datatype).toBe('类型');
54
+ expect(CHANGE_TYPE_LABELS.comment).toBe('注释');
55
+ expect(CHANGE_TYPE_LABELS.default).toBe('默认值');
56
+ });
57
+ });
58
+
59
+ describe('MySQL 表配置', () => {
60
+ test('应有默认配置', () => {
61
+ expect(MYSQL_TABLE_CONFIG.ENGINE).toBeDefined();
62
+ expect(MYSQL_TABLE_CONFIG.CHARSET).toBeDefined();
63
+ expect(MYSQL_TABLE_CONFIG.COLLATE).toBeDefined();
64
+ });
65
+
66
+ test('默认引擎应为 InnoDB', () => {
67
+ expect(MYSQL_TABLE_CONFIG.ENGINE).toMatch(/InnoDB/i);
68
+ });
69
+
70
+ test('默认字符集应为 utf8mb4', () => {
71
+ expect(MYSQL_TABLE_CONFIG.CHARSET).toMatch(/utf8mb4/i);
72
+ });
73
+ });
74
+
75
+ describe('数据库类型检测', () => {
76
+ test('IS_MYSQL, IS_PG, IS_SQLITE 三者只有一个为 true', () => {
77
+ const trueCount = [IS_MYSQL, IS_PG, IS_SQLITE].filter(Boolean).length;
78
+ expect(trueCount).toBe(1);
79
+ });
80
+
81
+ test('类型映射应包含所有字段类型', () => {
82
+ expect(typeMapping.number).toBeDefined();
83
+ expect(typeMapping.string).toBeDefined();
84
+ expect(typeMapping.text).toBeDefined();
85
+ expect(typeMapping.array).toBeDefined();
86
+ });
87
+
88
+ test('不同数据库的类型映射应不同', () => {
89
+ if (IS_MYSQL) {
90
+ expect(typeMapping.number).toBe('BIGINT');
91
+ expect(typeMapping.string).toBe('VARCHAR');
92
+ expect(typeMapping.text).toBe('MEDIUMTEXT');
93
+ } else if (IS_PG) {
94
+ expect(typeMapping.number).toBe('BIGINT');
95
+ expect(typeMapping.string).toBe('character varying');
96
+ expect(typeMapping.text).toBe('TEXT');
97
+ } else if (IS_SQLITE) {
98
+ expect(typeMapping.number).toBe('INTEGER');
99
+ expect(typeMapping.string).toBe('TEXT');
100
+ expect(typeMapping.text).toBe('TEXT');
101
+ }
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,134 @@
1
+ /**
2
+ * syncDb DDL 构建测试
3
+ */
4
+
5
+ import { describe, test, expect } from 'bun:test';
6
+ import { buildIndexSQL, buildSystemColumnDefs, buildBusinessColumnDefs, generateDDLClause, isPgCompatibleTypeChange } from '../ddl.js';
7
+ import { IS_MYSQL, IS_PG, IS_SQLITE } from '../constants.js';
8
+
9
+ describe('syncDb/ddl', () => {
10
+ describe('buildIndexSQL', () => {
11
+ test('应生成创建索引 SQL', () => {
12
+ const sql = buildIndexSQL('users', 'idx_email', 'email', 'create');
13
+ expect(sql).toBeDefined();
14
+ expect(sql.length).toBeGreaterThan(0);
15
+
16
+ if (IS_MYSQL) {
17
+ expect(sql).toContain('ADD INDEX');
18
+ expect(sql).toContain('ALGORITHM=INPLACE');
19
+ expect(sql).toContain('LOCK=NONE');
20
+ } else if (IS_PG) {
21
+ expect(sql).toContain('CREATE INDEX CONCURRENTLY');
22
+ } else if (IS_SQLITE) {
23
+ expect(sql).toContain('CREATE INDEX');
24
+ }
25
+ });
26
+
27
+ test('应生成删除索引 SQL', () => {
28
+ const sql = buildIndexSQL('users', 'idx_email', 'email', 'drop');
29
+ expect(sql).toBeDefined();
30
+
31
+ if (IS_MYSQL) {
32
+ expect(sql).toContain('DROP INDEX');
33
+ } else if (IS_PG) {
34
+ expect(sql).toContain('DROP INDEX CONCURRENTLY');
35
+ } else if (IS_SQLITE) {
36
+ expect(sql).toContain('DROP INDEX');
37
+ }
38
+ });
39
+ });
40
+
41
+ describe('buildSystemColumnDefs', () => {
42
+ test('应返回 5 个系统字段定义', () => {
43
+ const defs = buildSystemColumnDefs();
44
+ expect(defs).toHaveLength(5);
45
+ });
46
+
47
+ test('系统字段应包含 id, created_at, updated_at, deleted_at, state', () => {
48
+ const defs = buildSystemColumnDefs();
49
+ const combined = defs.join(' ');
50
+
51
+ expect(combined).toContain('id');
52
+ expect(combined).toContain('created_at');
53
+ expect(combined).toContain('updated_at');
54
+ expect(combined).toContain('deleted_at');
55
+ expect(combined).toContain('state');
56
+ });
57
+
58
+ test('MySQL 应包含 COMMENT', () => {
59
+ const defs = buildSystemColumnDefs();
60
+ const combined = defs.join(' ');
61
+
62
+ if (IS_MYSQL) {
63
+ expect(combined).toContain('COMMENT');
64
+ }
65
+ });
66
+ });
67
+
68
+ describe('buildBusinessColumnDefs', () => {
69
+ test('应处理空字段对象', () => {
70
+ const defs = buildBusinessColumnDefs({});
71
+ expect(defs).toHaveLength(0);
72
+ });
73
+
74
+ test('应生成字段定义', () => {
75
+ const fields = {
76
+ username: '用户名|string|1|50||1',
77
+ age: '年龄|number|0|150|0|0'
78
+ };
79
+ const defs = buildBusinessColumnDefs(fields);
80
+
81
+ expect(defs.length).toBeGreaterThan(0);
82
+ expect(defs.join(' ')).toContain('username');
83
+ expect(defs.join(' ')).toContain('age');
84
+ });
85
+ });
86
+
87
+ describe('generateDDLClause', () => {
88
+ test('应生成添加字段子句', () => {
89
+ const clause = generateDDLClause('email', '邮箱|string|0|100||0', true);
90
+ expect(clause).toBeDefined();
91
+
92
+ if (IS_MYSQL) {
93
+ expect(clause).toContain('ADD COLUMN');
94
+ } else if (IS_PG) {
95
+ expect(clause).toContain('ADD COLUMN');
96
+ } else if (IS_SQLITE) {
97
+ expect(clause).toContain('ADD COLUMN');
98
+ }
99
+ });
100
+
101
+ test('应生成修改字段子句', () => {
102
+ const clause = generateDDLClause('email', '邮箱|string|0|100||0', false);
103
+ expect(clause).toBeDefined();
104
+
105
+ if (IS_MYSQL) {
106
+ expect(clause).toContain('MODIFY COLUMN');
107
+ } else if (IS_PG) {
108
+ expect(clause).toContain('ALTER COLUMN');
109
+ }
110
+ });
111
+ });
112
+
113
+ describe('isPgCompatibleTypeChange', () => {
114
+ test('varchar -> text 应为兼容变更', () => {
115
+ const result = isPgCompatibleTypeChange('character varying', 'text');
116
+ expect(result).toBe(true);
117
+ });
118
+
119
+ test('text -> varchar 应为不兼容变更', () => {
120
+ const result = isPgCompatibleTypeChange('text', 'character varying');
121
+ expect(result).toBe(false);
122
+ });
123
+
124
+ test('相同类型应为不兼容变更(无需变更)', () => {
125
+ const result = isPgCompatibleTypeChange('text', 'text');
126
+ expect(result).toBe(false);
127
+ });
128
+
129
+ test('应处理大小写', () => {
130
+ const result = isPgCompatibleTypeChange('CHARACTER VARYING', 'TEXT');
131
+ expect(result).toBe(true);
132
+ });
133
+ });
134
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * syncDb 辅助函数测试
3
+ */
4
+
5
+ import { describe, test, expect } from 'bun:test';
6
+ import { quoteIdentifier, logFieldChange, formatFieldList } from '../helpers.js';
7
+
8
+ describe('syncDb/helpers', () => {
9
+ describe('quoteIdentifier', () => {
10
+ test('应正确引用标识符', () => {
11
+ const result = quoteIdentifier('user_table');
12
+
13
+ // 根据当前数据库类型验证
14
+ expect(typeof result).toBe('string');
15
+ expect(result.length).toBeGreaterThan(0);
16
+ });
17
+
18
+ test('应处理特殊字符', () => {
19
+ const result = quoteIdentifier('table_name_with_underscore');
20
+ expect(result).toBeDefined();
21
+ });
22
+
23
+ test('应处理空字符串', () => {
24
+ const result = quoteIdentifier('');
25
+ expect(typeof result).toBe('string');
26
+ });
27
+ });
28
+
29
+ describe('logFieldChange', () => {
30
+ test('应不抛出错误', () => {
31
+ expect(() => {
32
+ logFieldChange('test_table', 'test_field', 'length', 100, 200, '长度');
33
+ }).not.toThrow();
34
+ });
35
+
36
+ test('应处理各种变更类型', () => {
37
+ expect(() => {
38
+ logFieldChange('table1', 'field1', 'length', 100, 200, '长度');
39
+ logFieldChange('table2', 'field2', 'datatype', 'INT', 'VARCHAR', '类型');
40
+ logFieldChange('table3', 'field3', 'comment', 'old', 'new', '注释');
41
+ logFieldChange('table4', 'field4', 'default', 0, 1, '默认值');
42
+ }).not.toThrow();
43
+ });
44
+ });
45
+
46
+ describe('formatFieldList', () => {
47
+ test('应正确格式化单个字段', () => {
48
+ const result = formatFieldList(['id']);
49
+ expect(result).toBeDefined();
50
+ expect(typeof result).toBe('string');
51
+ });
52
+
53
+ test('应正确格式化多个字段', () => {
54
+ const result = formatFieldList(['id', 'name', 'email']);
55
+ expect(result).toContain(',');
56
+ expect(result.split(',').length).toBe(3);
57
+ });
58
+
59
+ test('应处理空数组', () => {
60
+ const result = formatFieldList([]);
61
+ expect(result).toBe('');
62
+ });
63
+
64
+ test('应正确引用字段名', () => {
65
+ const result = formatFieldList(['user_id', 'user_name']);
66
+ expect(result).toBeDefined();
67
+ // 结果应包含引用符号(取决于数据库类型)
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * syncDb 类型定义模块
3
+ *
4
+ * 集中管理核心类型定义
5
+ */
6
+
7
+ // ==================== 数据库相关类型 ====================
8
+
9
+ /**
10
+ * 列信息接口
11
+ */
12
+ export interface ColumnInfo {
13
+ type: string;
14
+ columnType: string;
15
+ length: number | null;
16
+ nullable: boolean;
17
+ defaultValue: any;
18
+ comment: string | null;
19
+ }
20
+
21
+ /**
22
+ * 索引信息接口(键为索引名,值为列名数组)
23
+ */
24
+ export interface IndexInfo {
25
+ [indexName: string]: string[];
26
+ }
27
+
28
+ // ==================== 变更相关类型 ====================
29
+
30
+ /**
31
+ * 字段变更接口
32
+ */
33
+ export interface FieldChange {
34
+ type: 'length' | 'datatype' | 'comment' | 'default';
35
+ current: any;
36
+ expected: any;
37
+ }
38
+
39
+ /**
40
+ * 索引操作接口
41
+ */
42
+ export interface IndexAction {
43
+ action: 'create' | 'drop';
44
+ indexName: string;
45
+ fieldName: string;
46
+ }
47
+
48
+ /**
49
+ * 表变更计划接口
50
+ */
51
+ export interface TablePlan {
52
+ changed: boolean;
53
+ addClauses: string[];
54
+ modifyClauses: string[];
55
+ defaultClauses: string[];
56
+ indexActions: IndexAction[];
57
+ commentActions?: string[];
58
+ }
59
+
60
+ // ==================== 统计相关类型 ====================
61
+
62
+ /**
63
+ * 全局统计对象
64
+ */
65
+ export interface GlobalStats {
66
+ processedTables: number;
67
+ createdTables: number;
68
+ modifiedTables: number;
69
+ addFields: number;
70
+ nameChanges: number;
71
+ typeChanges: number;
72
+ minChanges: number;
73
+ maxChanges: number;
74
+ defaultChanges: number;
75
+ indexCreate: number;
76
+ indexDrop: number;
77
+ }
78
+
79
+ // ==================== 解析相关类型 ====================
80
+
81
+ /**
82
+ * 解析后的字段规则
83
+ */
84
+ export interface ParsedFieldRule {
85
+ name: string;
86
+ type: string;
87
+ min: any;
88
+ max: any;
89
+ default: any;
90
+ index: number;
91
+ regex: string;
92
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * syncDb 数据库版本检查模块
3
+ *
4
+ * 包含:
5
+ * - 数据库版本验证(MySQL/PostgreSQL/SQLite)
6
+ */
7
+
8
+ import { Logger } from '../../utils/logger.js';
9
+ import { Env } from '../../config/env.js';
10
+ import { DB_VERSION_REQUIREMENTS, IS_MYSQL, IS_PG, IS_SQLITE } from './constants.js';
11
+ import type { SQL } from 'bun';
12
+
13
+ /**
14
+ * 数据库版本检查(按方言)
15
+ *
16
+ * 根据当前数据库类型检查版本是否符合最低要求:
17
+ * - MySQL: >= 8.0
18
+ * - PostgreSQL: >= 17
19
+ * - SQLite: >= 3.50.0
20
+ *
21
+ * @param sql - SQL 客户端实例
22
+ * @throws {Error} 如果数据库版本不符合要求或无法获取版本信息
23
+ */
24
+ export async function ensureDbVersion(sql: SQL): Promise<void> {
25
+ if (!sql) throw new Error('SQL 客户端未初始化');
26
+
27
+ if (IS_MYSQL) {
28
+ const r = await sql`SELECT VERSION() AS version`;
29
+ if (!r || r.length === 0 || !r[0]?.version) {
30
+ throw new Error('无法获取 MySQL 版本信息');
31
+ }
32
+ const version = r[0].version;
33
+ const majorVersion = parseInt(String(version).split('.')[0], 10);
34
+ if (!Number.isFinite(majorVersion) || majorVersion < DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR) {
35
+ throw new Error(`此脚本仅支持 MySQL ${DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR}.0+,当前版本: ${version}`);
36
+ }
37
+ Logger.info(`MySQL 版本: ${version}`);
38
+ return;
39
+ }
40
+
41
+ if (IS_PG) {
42
+ const r = await sql`SELECT version() AS version`;
43
+ if (!r || r.length === 0 || !r[0]?.version) {
44
+ throw new Error('无法获取 PostgreSQL 版本信息');
45
+ }
46
+ const versionText = r[0].version;
47
+ Logger.info(`PostgreSQL 版本: ${versionText}`);
48
+ const m = /PostgreSQL\s+(\d+)/i.exec(versionText);
49
+ const major = m ? parseInt(m[1], 10) : NaN;
50
+ if (!Number.isFinite(major) || major < DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR) {
51
+ throw new Error(`此脚本要求 PostgreSQL >= ${DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR},当前: ${versionText}`);
52
+ }
53
+ return;
54
+ }
55
+
56
+ if (IS_SQLITE) {
57
+ const r = await sql`SELECT sqlite_version() AS version`;
58
+ if (!r || r.length === 0 || !r[0]?.version) {
59
+ throw new Error('无法获取 SQLite 版本信息');
60
+ }
61
+ const version = r[0].version;
62
+ Logger.info(`SQLite 版本: ${version}`);
63
+ // 强制最低版本:SQLite ≥ 3.50.0
64
+ const [maj, min, patch] = String(version)
65
+ .split('.')
66
+ .map((v) => parseInt(v, 10) || 0);
67
+ const vnum = maj * 10000 + min * 100 + patch; // 3.50.0 -> 35000
68
+ if (!Number.isFinite(vnum) || vnum < DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION_NUM) {
69
+ throw new Error(`此脚本要求 SQLite >= ${DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION},当前: ${version}`);
70
+ }
71
+ return;
72
+ }
73
+ }
@@ -0,0 +1,9 @@
1
+ import { SyncDb } from './syncDb/index.js';
2
+
3
+ // 如果直接运行此脚本
4
+ if (import.meta.main) {
5
+ SyncDb().catch((error) => {
6
+ console.error('❌ 数据库同步失败:', error);
7
+ process.exit(1);
8
+ });
9
+ }