befly 3.8.25 → 3.8.29

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 (62) hide show
  1. package/config.ts +8 -9
  2. package/hooks/{rateLimit.ts → _rateLimit.ts} +7 -13
  3. package/hooks/auth.ts +3 -11
  4. package/hooks/cors.ts +1 -4
  5. package/hooks/parser.ts +6 -8
  6. package/hooks/permission.ts +9 -12
  7. package/hooks/validator.ts +6 -9
  8. package/lib/cacheHelper.ts +0 -4
  9. package/lib/{database.ts → connect.ts} +65 -18
  10. package/lib/logger.ts +1 -17
  11. package/lib/redisHelper.ts +6 -5
  12. package/loader/loadApis.ts +3 -3
  13. package/loader/loadHooks.ts +15 -41
  14. package/loader/loadPlugins.ts +10 -16
  15. package/main.ts +25 -28
  16. package/package.json +4 -4
  17. package/plugins/cache.ts +2 -2
  18. package/plugins/cipher.ts +15 -0
  19. package/plugins/config.ts +16 -0
  20. package/plugins/db.ts +7 -17
  21. package/plugins/jwt.ts +15 -0
  22. package/plugins/logger.ts +1 -1
  23. package/plugins/redis.ts +4 -4
  24. package/plugins/tool.ts +50 -0
  25. package/router/api.ts +56 -42
  26. package/router/static.ts +12 -12
  27. package/sync/syncAll.ts +2 -20
  28. package/sync/syncApi.ts +7 -7
  29. package/sync/syncDb/apply.ts +10 -12
  30. package/sync/syncDb/constants.ts +64 -12
  31. package/sync/syncDb/ddl.ts +9 -8
  32. package/sync/syncDb/helpers.ts +7 -119
  33. package/sync/syncDb/schema.ts +16 -19
  34. package/sync/syncDb/sqlite.ts +1 -3
  35. package/sync/syncDb/table.ts +13 -146
  36. package/sync/syncDb/tableCreate.ts +28 -12
  37. package/sync/syncDb/types.ts +126 -0
  38. package/sync/syncDb/version.ts +4 -7
  39. package/sync/syncDb.ts +151 -6
  40. package/sync/syncDev.ts +19 -15
  41. package/sync/syncMenu.ts +87 -75
  42. package/tests/redisHelper.test.ts +15 -16
  43. package/tests/sync-connection.test.ts +189 -0
  44. package/tests/syncDb-apply.test.ts +288 -0
  45. package/tests/syncDb-constants.test.ts +151 -0
  46. package/tests/syncDb-ddl.test.ts +206 -0
  47. package/tests/syncDb-helpers.test.ts +113 -0
  48. package/tests/syncDb-schema.test.ts +178 -0
  49. package/tests/syncDb-types.test.ts +130 -0
  50. package/tsconfig.json +2 -2
  51. package/types/api.d.ts +1 -1
  52. package/types/befly.d.ts +23 -21
  53. package/types/common.d.ts +0 -29
  54. package/types/context.d.ts +8 -6
  55. package/types/hook.d.ts +3 -4
  56. package/types/plugin.d.ts +3 -0
  57. package/hooks/errorHandler.ts +0 -23
  58. package/hooks/requestId.ts +0 -24
  59. package/hooks/requestLogger.ts +0 -25
  60. package/hooks/responseFormatter.ts +0 -64
  61. package/router/root.ts +0 -56
  62. package/sync/syncDb/index.ts +0 -164
package/sync/syncApi.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { readdirSync, statSync } from 'node:fs';
14
14
  import { join, dirname, relative, basename } from 'pathe';
15
- import { Database } from '../lib/database.js';
15
+ import { Connect } from '../lib/connect.js';
16
16
  import { RedisHelper } from '../lib/redisHelper.js';
17
17
  import { scanFiles, scanAddons, addonDirExists, getAddonDir } from 'befly-util';
18
18
 
@@ -106,11 +106,11 @@ async function scanAllApis(projectRoot: string): Promise<ApiInfo[]> {
106
106
  const addonApisDir = getAddonDir(addonName, 'apis');
107
107
 
108
108
  // 读取 addon 配置
109
- const addonConfigPath = getAddonDir(addonName, 'addon.config.json');
109
+ const addonPackageJsonPath = getAddonDir(addonName, 'package.json');
110
110
  let addonTitle = addonName;
111
111
  try {
112
- const config = await import(addonConfigPath, { with: { type: 'json' } });
113
- addonTitle = config.default.title || addonName;
112
+ const packageJson = await import(addonPackageJsonPath, { with: { type: 'json' } });
113
+ addonTitle = packageJson.default?.title || addonName;
114
114
  } catch (error) {
115
115
  // 忽略配置读取错误
116
116
  }
@@ -220,9 +220,9 @@ export async function syncApiCommand(config: BeflyOptions, options: SyncApiOptio
220
220
  }
221
221
 
222
222
  // 连接数据库(SQL + Redis)
223
- await Database.connect();
223
+ await Connect.connect(config);
224
224
 
225
- const helper = Database.getDbHelper();
225
+ const helper = Connect.getDbHelper();
226
226
 
227
227
  // 1. 检查表是否存在(addon_admin_api 来自 addon-admin 组件)
228
228
  const exists = await helper.tableExists('addon_admin_api');
@@ -259,6 +259,6 @@ export async function syncApiCommand(config: BeflyOptions, options: SyncApiOptio
259
259
  Logger.error('API 同步失败:', error);
260
260
  throw error;
261
261
  } finally {
262
- await Database?.disconnect();
262
+ await Connect.disconnect();
263
263
  }
264
264
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { Logger } from '../../lib/logger.js';
10
- import { IS_MYSQL, IS_PG, IS_SQLITE, CHANGE_TYPE_LABELS, typeMapping } from './constants.js';
10
+ import { isMySQL, isPG, isSQLite, IS_PLAN, CHANGE_TYPE_LABELS, getTypeMapping } from './constants.js';
11
11
  import { logFieldChange, resolveDefaultValue, isStringOrArrayType } from './helpers.js';
12
12
  import { executeDDLSafely, buildIndexSQL } from './ddl.js';
13
13
  import { rebuildSqliteTable } from './sqlite.js';
@@ -15,9 +15,6 @@ import type { FieldChange, IndexAction, TablePlan, ColumnInfo } from '../../type
15
15
  import type { SQL } from 'bun';
16
16
  import type { FieldDefinition } from 'befly/types/common';
17
17
 
18
- // 是否为计划模式(从环境变量读取)
19
- const IS_PLAN = process.argv.includes('--plan');
20
-
21
18
  /**
22
19
  * 构建 ALTER TABLE SQL 语句
23
20
  *
@@ -30,7 +27,7 @@ const IS_PLAN = process.argv.includes('--plan');
30
27
  * @returns 完整的 ALTER TABLE 语句
31
28
  */
32
29
  function buildAlterTableSQL(tableName: string, clauses: string[]): string {
33
- if (IS_MYSQL) {
30
+ if (isMySQL()) {
34
31
  return `ALTER TABLE \`${tableName}\` ${clauses.join(', ')}, ALGORITHM=INSTANT, LOCK=NONE`;
35
32
  }
36
33
  return `ALTER TABLE "${tableName}" ${clauses.join(', ')}`;
@@ -54,7 +51,7 @@ export function compareFieldDefinition(existingColumn: ColumnInfo, fieldDef: Fie
54
51
  const changes: FieldChange[] = [];
55
52
 
56
53
  // 检查长度变化(string和array类型) - SQLite 不比较长度
57
- if (!IS_SQLITE && isStringOrArrayType(fieldDef.type)) {
54
+ if (!isSQLite() && isStringOrArrayType(fieldDef.type)) {
58
55
  if (existingColumn.max !== fieldDef.max) {
59
56
  changes.push({
60
57
  type: 'length',
@@ -65,7 +62,7 @@ export function compareFieldDefinition(existingColumn: ColumnInfo, fieldDef: Fie
65
62
  }
66
63
 
67
64
  // 检查注释变化(MySQL/PG 支持列注释)
68
- if (!IS_SQLITE) {
65
+ if (!isSQLite()) {
69
66
  const currentComment = existingColumn.comment || '';
70
67
  if (currentComment !== fieldDef.name) {
71
68
  changes.push({
@@ -77,6 +74,7 @@ export function compareFieldDefinition(existingColumn: ColumnInfo, fieldDef: Fie
77
74
  }
78
75
 
79
76
  // 检查数据类型变化(只对比基础类型)
77
+ const typeMapping = getTypeMapping();
80
78
  const expectedType = typeMapping[fieldDef.type].toLowerCase();
81
79
  const currentType = existingColumn.type.toLowerCase();
82
80
 
@@ -130,7 +128,7 @@ export async function applyTablePlan(sql: SQL, tableName: string, fields: Record
130
128
  if (!plan || !plan.changed) return;
131
129
 
132
130
  // SQLite: 仅支持部分 ALTER;需要时走重建
133
- if (IS_SQLITE) {
131
+ if (isSQLite()) {
134
132
  if (plan.modifyClauses.length > 0 || plan.defaultClauses.length > 0) {
135
133
  if (IS_PLAN) Logger.debug(`[计划] 重建表 ${tableName} 以应用列修改/默认值变化`);
136
134
  else await rebuildSqliteTable(sql, tableName, fields);
@@ -146,19 +144,19 @@ export async function applyTablePlan(sql: SQL, tableName: string, fields: Record
146
144
  if (clauses.length > 0) {
147
145
  const stmt = buildAlterTableSQL(tableName, clauses);
148
146
  if (IS_PLAN) Logger.debug(`[计划] ${stmt}`);
149
- else if (IS_MYSQL) await executeDDLSafely(sql, stmt);
147
+ else if (isMySQL()) await executeDDLSafely(sql, stmt);
150
148
  else await sql.unsafe(stmt);
151
149
  }
152
150
  }
153
151
 
154
152
  // 默认值专用 ALTER(SQLite 不支持)
155
153
  if (plan.defaultClauses.length > 0) {
156
- if (IS_SQLITE) {
154
+ if (isSQLite()) {
157
155
  Logger.warn(`SQLite 不支持修改默认值,表 ${tableName} 的默认值变更已跳过`);
158
156
  } else {
159
157
  const stmt = buildAlterTableSQL(tableName, plan.defaultClauses);
160
158
  if (IS_PLAN) Logger.debug(`[计划] ${stmt}`);
161
- else if (IS_MYSQL) await executeDDLSafely(sql, stmt);
159
+ else if (isMySQL()) await executeDDLSafely(sql, stmt);
162
160
  else await sql.unsafe(stmt);
163
161
  }
164
162
  }
@@ -185,7 +183,7 @@ export async function applyTablePlan(sql: SQL, tableName: string, fields: Record
185
183
  }
186
184
 
187
185
  // PG 列注释
188
- if (IS_PG && plan.commentActions && plan.commentActions.length > 0) {
186
+ if (isPG() && plan.commentActions && plan.commentActions.length > 0) {
189
187
  for (const stmt of plan.commentActions) {
190
188
  if (IS_PLAN) Logger.info(`[计划] ${stmt}`);
191
189
  else await sql.unsafe(stmt);
@@ -60,17 +60,69 @@ export const MYSQL_TABLE_CONFIG = {
60
60
  COLLATE: 'utf8mb4_0900_ai_ci'
61
61
  } as const;
62
62
 
63
- // 数据库类型判断
64
- export const DB = (process.env.DB_TYPE || 'mysql').toLowerCase();
65
- export const IS_MYSQL = DB === 'mysql';
66
- export const IS_PG = DB === 'postgresql' || DB === 'postgres';
67
- export const IS_SQLITE = DB === 'sqlite';
63
+ // 是否为计划模式(仅输出 SQL 不执行)
64
+ export const IS_PLAN = process.argv.includes('--plan');
68
65
 
69
- // 字段类型映射(按方言)
70
- export const typeMapping = {
71
- number: IS_SQLITE ? 'INTEGER' : IS_PG ? 'BIGINT' : 'BIGINT',
72
- string: IS_SQLITE ? 'TEXT' : IS_PG ? 'character varying' : 'VARCHAR',
73
- text: IS_MYSQL ? 'MEDIUMTEXT' : 'TEXT',
74
- array_string: IS_SQLITE ? 'TEXT' : IS_PG ? 'character varying' : 'VARCHAR',
75
- array_text: IS_MYSQL ? 'MEDIUMTEXT' : 'TEXT'
66
+ // 数据库类型(运行时设置,默认 mysql)
67
+ let _dbType: string = 'mysql';
68
+
69
+ /**
70
+ * 设置数据库类型(由 syncDbCommand 调用)
71
+ * @param dbType - 数据库类型(mysql/postgresql/postgres/sqlite)
72
+ */
73
+ export function setDbType(dbType: string): void {
74
+ _dbType = (dbType || 'mysql').toLowerCase();
75
+ }
76
+
77
+ /**
78
+ * 获取当前数据库类型
79
+ */
80
+ export function getDbType(): string {
81
+ return _dbType;
82
+ }
83
+
84
+ // 数据库类型判断(getter 函数,运行时动态计算)
85
+ export function isMySQL(): boolean {
86
+ return _dbType === 'mysql';
87
+ }
88
+
89
+ export function isPG(): boolean {
90
+ return _dbType === 'postgresql' || _dbType === 'postgres';
91
+ }
92
+
93
+ export function isSQLite(): boolean {
94
+ return _dbType === 'sqlite';
95
+ }
96
+
97
+ // 兼容旧代码的静态别名(通过 getter 实现动态获取)
98
+ export const DB_TYPE = {
99
+ get current(): string {
100
+ return _dbType;
101
+ },
102
+ get IS_MYSQL(): boolean {
103
+ return isMySQL();
104
+ },
105
+ get IS_PG(): boolean {
106
+ return isPG();
107
+ },
108
+ get IS_SQLITE(): boolean {
109
+ return isSQLite();
110
+ }
76
111
  };
112
+
113
+ /**
114
+ * 获取字段类型映射(根据当前数据库类型)
115
+ */
116
+ export function getTypeMapping(): Record<string, string> {
117
+ const isSqlite = isSQLite();
118
+ const isPg = isPG();
119
+ const isMysql = isMySQL();
120
+
121
+ return {
122
+ number: isSqlite ? 'INTEGER' : isPg ? 'BIGINT' : 'BIGINT',
123
+ string: isSqlite ? 'TEXT' : isPg ? 'character varying' : 'VARCHAR',
124
+ text: isMysql ? 'MEDIUMTEXT' : 'TEXT',
125
+ array_string: isSqlite ? 'TEXT' : isPg ? 'character varying' : 'VARCHAR',
126
+ array_text: isMysql ? 'MEDIUMTEXT' : 'TEXT'
127
+ };
128
+ }
@@ -10,8 +10,9 @@
10
10
 
11
11
  import { snakeCase } from 'es-toolkit/string';
12
12
  import { Logger } from '../../lib/logger.js';
13
- import { IS_MYSQL, IS_PG, typeMapping } from './constants.js';
14
- import { quoteIdentifier, resolveDefaultValue, generateDefaultSql, getSqlType, escapeComment } from './helpers.js';
13
+ import { isMySQL, isPG, getTypeMapping } from './constants.js';
14
+ import { quoteIdentifier, escapeComment } from './helpers.js';
15
+ import { resolveDefaultValue, generateDefaultSql, getSqlType } from './types.js';
15
16
 
16
17
  import type { SQL } from 'bun';
17
18
  import type { FieldDefinition, AnyObject } from 'befly/types/common.js';
@@ -30,7 +31,7 @@ export function buildIndexSQL(tableName: string, indexName: string, fieldName: s
30
31
  const indexQuoted = quoteIdentifier(indexName);
31
32
  const fieldQuoted = quoteIdentifier(fieldName);
32
33
 
33
- if (IS_MYSQL) {
34
+ if (isMySQL()) {
34
35
  const parts = [];
35
36
  if (action === 'create') {
36
37
  parts.push(`ADD INDEX ${indexQuoted} (${fieldQuoted})`);
@@ -43,7 +44,7 @@ export function buildIndexSQL(tableName: string, indexName: string, fieldName: s
43
44
  return `ALTER TABLE ${tableQuoted} ${parts.join(', ')}`;
44
45
  }
45
46
 
46
- if (IS_PG) {
47
+ if (isPG()) {
47
48
  if (action === 'create') {
48
49
  // 始终使用 CONCURRENTLY
49
50
  return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${indexQuoted} ON ${tableQuoted}(${fieldQuoted})`;
@@ -64,7 +65,7 @@ export function buildIndexSQL(tableName: string, indexName: string, fieldName: s
64
65
  * @returns 系统字段的列定义数组
65
66
  */
66
67
  export function buildSystemColumnDefs(): string[] {
67
- if (IS_MYSQL) {
68
+ if (isMySQL()) {
68
69
  return ['`id` BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT "主键ID"', '`created_at` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT "创建时间"', '`updated_at` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT "更新时间"', '`deleted_at` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT "删除时间"', '`state` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT "状态字段"'];
69
70
  }
70
71
  return ['"id" INTEGER PRIMARY KEY', '"created_at" INTEGER NOT NULL DEFAULT 0', '"updated_at" INTEGER NOT NULL DEFAULT 0', '"deleted_at" INTEGER NOT NULL DEFAULT 0', '"state" INTEGER NOT NULL DEFAULT 0'];
@@ -93,7 +94,7 @@ export function buildBusinessColumnDefs(fields: Record<string, FieldDefinition>)
93
94
  const uniqueSql = fieldDef.unique ? ' UNIQUE' : '';
94
95
  const nullableSql = fieldDef.nullable ? ' NULL' : ' NOT NULL';
95
96
 
96
- if (IS_MYSQL) {
97
+ if (isMySQL()) {
97
98
  colDefs.push(`\`${dbFieldName}\` ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(fieldDef.name)}"`);
98
99
  } else {
99
100
  colDefs.push(`"${dbFieldName}" ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`);
@@ -125,10 +126,10 @@ export function generateDDLClause(fieldKey: string, fieldDef: FieldDefinition, i
125
126
  const uniqueSql = fieldDef.unique ? ' UNIQUE' : '';
126
127
  const nullableSql = fieldDef.nullable ? ' NULL' : ' NOT NULL';
127
128
 
128
- if (IS_MYSQL) {
129
+ if (isMySQL()) {
129
130
  return `${isAdd ? 'ADD COLUMN' : 'MODIFY COLUMN'} \`${dbFieldName}\` ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(fieldDef.name)}"`;
130
131
  }
131
- if (IS_PG) {
132
+ if (isPG()) {
132
133
  if (isAdd) return `ADD COLUMN IF NOT EXISTS "${dbFieldName}" ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`;
133
134
  // PG 修改:类型与非空可分条执行,生成 TYPE 改变;非空另由上层统一控制
134
135
  return `ALTER COLUMN "${dbFieldName}" TYPE ${sqlType}`;
@@ -3,13 +3,16 @@
3
3
  *
4
4
  * 包含:
5
5
  * - 标识符引用(反引号/双引号转义)
6
- * - 默认值处理
7
6
  * - 日志输出格式化
7
+ * - 字段默认值应用
8
8
  */
9
9
 
10
- import { IS_MYSQL, IS_PG, typeMapping } from './constants.js';
10
+ import { isMySQL, isPG } from './constants.js';
11
11
  import { Logger } from '../../lib/logger.js';
12
12
 
13
+ // 从 types.ts 重新导出,保持向后兼容
14
+ export { isStringOrArrayType, getSqlType, resolveDefaultValue, generateDefaultSql } from './types.js';
15
+
13
16
  /**
14
17
  * 根据数据库类型引用标识符
15
18
  *
@@ -23,126 +26,11 @@ import { Logger } from '../../lib/logger.js';
23
26
  * // SQLite: user_table
24
27
  */
25
28
  export function quoteIdentifier(identifier: string): string {
26
- if (IS_MYSQL) return `\`${identifier}\``;
27
- if (IS_PG) return `"${identifier}"`;
29
+ if (isMySQL()) return `\`${identifier}\``;
30
+ if (isPG()) return `"${identifier}"`;
28
31
  return identifier; // SQLite 无需引用
29
32
  }
30
33
 
31
- /**
32
- * 处理默认值:将 null 或 'null' 字符串转换为对应类型的默认值
33
- *
34
- * @param fieldDefault - 字段默认值(可能是 null 或 'null' 字符串)
35
- * @param fieldType - 字段类型(number/string/text/array)
36
- * @returns 实际默认值
37
- *
38
- * @example
39
- * resolveDefaultValue(null, 'string') // => ''
40
- * resolveDefaultValue(null, 'number') // => 0
41
- * resolveDefaultValue('null', 'number') // => 0
42
- * resolveDefaultValue(null, 'array') // => '[]'
43
- * resolveDefaultValue(null, 'text') // => 'null'
44
- * resolveDefaultValue('admin', 'string') // => 'admin'
45
- * resolveDefaultValue(0, 'number') // => 0
46
- */
47
- export function resolveDefaultValue(fieldDefault: any, fieldType: 'number' | 'string' | 'text' | 'array'): any {
48
- // null 或字符串 'null' 都表示使用类型默认值
49
- if (fieldDefault !== null && fieldDefault !== 'null') {
50
- return fieldDefault;
51
- }
52
-
53
- // null 表示使用类型默认值
54
- switch (fieldType) {
55
- case 'number':
56
- return 0;
57
- case 'string':
58
- return '';
59
- case 'array':
60
- return '[]';
61
- case 'text':
62
- // text 类型不设置默认值,保持 'null'
63
- return 'null';
64
- default:
65
- return fieldDefault;
66
- }
67
- }
68
-
69
- /**
70
- * 生成 SQL DEFAULT 子句
71
- *
72
- * @param actualDefault - 实际默认值(已经过 resolveDefaultValue 处理)
73
- * @param fieldType - 字段类型
74
- * @returns SQL DEFAULT 子句字符串(包含前导空格),如果不需要则返回空字符串
75
- *
76
- * @example
77
- * generateDefaultSql(0, 'number') // => ' DEFAULT 0'
78
- * generateDefaultSql('admin', 'string') // => " DEFAULT 'admin'"
79
- * generateDefaultSql('', 'string') // => " DEFAULT ''"
80
- * generateDefaultSql('null', 'text') // => ''
81
- */
82
- export function generateDefaultSql(actualDefault: any, fieldType: 'number' | 'string' | 'text' | 'array'): string {
83
- // text 类型不设置默认值
84
- if (fieldType === 'text' || actualDefault === 'null') {
85
- return '';
86
- }
87
-
88
- // 仅 number/string/array 类型设置默认值
89
- if (fieldType === 'number' || fieldType === 'string' || fieldType === 'array') {
90
- if (typeof actualDefault === 'number' && !Number.isNaN(actualDefault)) {
91
- return ` DEFAULT ${actualDefault}`;
92
- } else {
93
- // 字符串需要转义单引号:' -> ''
94
- const escaped = String(actualDefault).replace(/'/g, "''");
95
- return ` DEFAULT '${escaped}'`;
96
- }
97
- }
98
-
99
- return '';
100
- }
101
-
102
- /**
103
- * 判断是否为字符串或数组类型(需要长度参数)
104
- *
105
- * @param fieldType - 字段类型
106
- * @returns 是否为字符串或数组类型
107
- *
108
- * @example
109
- * isStringOrArrayType('string') // => true
110
- * isStringOrArrayType('array_string') // => true
111
- * isStringOrArrayType('array_text') // => false
112
- * isStringOrArrayType('number') // => false
113
- * isStringOrArrayType('text') // => false
114
- */
115
- export function isStringOrArrayType(fieldType: string): boolean {
116
- return fieldType === 'string' || fieldType === 'array_string';
117
- }
118
-
119
- /**
120
- * 获取 SQL 数据类型
121
- *
122
- * @param fieldType - 字段类型(number/string/text/array_string/array_text)
123
- * @param fieldMax - 最大长度(string/array_string 类型需要)
124
- * @param unsigned - 是否无符号(仅 MySQL number 类型有效)
125
- * @returns SQL 类型字符串
126
- *
127
- * @example
128
- * getSqlType('string', 100) // => 'VARCHAR(100)'
129
- * getSqlType('number', null, true) // => 'BIGINT UNSIGNED'
130
- * getSqlType('text', null) // => 'MEDIUMTEXT'
131
- * getSqlType('array_string', 500) // => 'VARCHAR(500)'
132
- * getSqlType('array_text', null) // => 'MEDIUMTEXT'
133
- */
134
- export function getSqlType(fieldType: string, fieldMax: number | null, unsigned: boolean = false): string {
135
- if (isStringOrArrayType(fieldType)) {
136
- return `${typeMapping[fieldType]}(${fieldMax})`;
137
- }
138
- // 处理 UNSIGNED 修饰符(仅 MySQL number 类型)
139
- const baseType = typeMapping[fieldType] || 'TEXT';
140
- if (IS_MYSQL && fieldType === 'number' && unsigned) {
141
- return `${baseType} UNSIGNED`;
142
- }
143
- return baseType;
144
- }
145
-
146
34
  /**
147
35
  * 转义 SQL 注释中的双引号
148
36
  *
@@ -7,7 +7,7 @@
7
7
  * - 获取表的索引信息
8
8
  */
9
9
 
10
- import { IS_MYSQL, IS_PG, IS_SQLITE } from './constants.js';
10
+ import { isMySQL, isPG, isSQLite } from './constants.js';
11
11
  import type { ColumnInfo, IndexInfo } from '../../types.js';
12
12
  import type { SQL } from 'bun';
13
13
 
@@ -19,22 +19,21 @@ import type { SQL } from 'bun';
19
19
  * @param dbName - 数据库名称
20
20
  * @returns 表是否存在
21
21
  */
22
- export async function tableExists(sql: SQL, tableName: string, dbName?: string): Promise<boolean> {
22
+ export async function tableExists(sql: SQL, tableName: string, dbName: string): Promise<boolean> {
23
23
  if (!sql) throw new Error('SQL 客户端未初始化');
24
- const database = dbName || process.env.DB_NAME;
25
24
 
26
25
  try {
27
- if (IS_MYSQL) {
28
- const res = await sql`SELECT COUNT(*) AS count FROM information_schema.TABLES WHERE TABLE_SCHEMA = ${database} AND TABLE_NAME = ${tableName}`;
26
+ if (isMySQL()) {
27
+ const res = await sql`SELECT COUNT(*) AS count FROM information_schema.TABLES WHERE TABLE_SCHEMA = ${dbName} AND TABLE_NAME = ${tableName}`;
29
28
  return (res[0]?.count || 0) > 0;
30
29
  }
31
30
 
32
- if (IS_PG) {
31
+ if (isPG()) {
33
32
  const res = await sql`SELECT COUNT(*)::int AS count FROM information_schema.tables WHERE table_schema = 'public' AND table_name = ${tableName}`;
34
33
  return (res[0]?.count || 0) > 0;
35
34
  }
36
35
 
37
- if (IS_SQLITE) {
36
+ if (isSQLite()) {
38
37
  const res = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name = ${tableName}`;
39
38
  return res.length > 0;
40
39
  }
@@ -61,16 +60,15 @@ export async function tableExists(sql: SQL, tableName: string, dbName?: string):
61
60
  * @param dbName - 数据库名称
62
61
  * @returns 列信息对象,键为列名,值为列详情
63
62
  */
64
- export async function getTableColumns(sql: SQL, tableName: string, dbName?: string): Promise<{ [key: string]: ColumnInfo }> {
63
+ export async function getTableColumns(sql: SQL, tableName: string, dbName: string): Promise<{ [key: string]: ColumnInfo }> {
65
64
  const columns: { [key: string]: ColumnInfo } = {};
66
- const database = dbName || process.env.DB_NAME;
67
65
 
68
66
  try {
69
- if (IS_MYSQL) {
67
+ if (isMySQL()) {
70
68
  const result = await sql`
71
69
  SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT, COLUMN_TYPE
72
70
  FROM information_schema.COLUMNS
73
- WHERE TABLE_SCHEMA = ${database} AND TABLE_NAME = ${tableName}
71
+ WHERE TABLE_SCHEMA = ${dbName} AND TABLE_NAME = ${tableName}
74
72
  ORDER BY ORDINAL_POSITION
75
73
  `;
76
74
  for (const row of result) {
@@ -91,7 +89,7 @@ export async function getTableColumns(sql: SQL, tableName: string, dbName?: stri
91
89
  comment: row.COLUMN_COMMENT
92
90
  };
93
91
  }
94
- } else if (IS_PG) {
92
+ } else if (isPG()) {
95
93
  const result = await sql`
96
94
  SELECT column_name, data_type, character_maximum_length, is_nullable, column_default
97
95
  FROM information_schema.columns
@@ -119,7 +117,7 @@ export async function getTableColumns(sql: SQL, tableName: string, dbName?: stri
119
117
  comment: commentMap[row.column_name] ?? null
120
118
  };
121
119
  }
122
- } else if (IS_SQLITE) {
120
+ } else if (isSQLite()) {
123
121
  const result = await sql.unsafe(`PRAGMA table_info(${tableName})`);
124
122
  for (const row of result) {
125
123
  let baseType = String(row.type || '').toUpperCase();
@@ -154,16 +152,15 @@ export async function getTableColumns(sql: SQL, tableName: string, dbName?: stri
154
152
  * @param dbName - 数据库名称
155
153
  * @returns 索引信息对象,键为索引名,值为列名数组
156
154
  */
157
- export async function getTableIndexes(sql: SQL, tableName: string, dbName?: string): Promise<IndexInfo> {
155
+ export async function getTableIndexes(sql: SQL, tableName: string, dbName: string): Promise<IndexInfo> {
158
156
  const indexes: IndexInfo = {};
159
- const database = dbName || process.env.DB_NAME;
160
157
 
161
158
  try {
162
- if (IS_MYSQL) {
159
+ if (isMySQL()) {
163
160
  const result = await sql`
164
161
  SELECT INDEX_NAME, COLUMN_NAME
165
162
  FROM information_schema.STATISTICS
166
- WHERE TABLE_SCHEMA = ${database}
163
+ WHERE TABLE_SCHEMA = ${dbName}
167
164
  AND TABLE_NAME = ${tableName}
168
165
  AND INDEX_NAME != 'PRIMARY'
169
166
  ORDER BY INDEX_NAME
@@ -172,7 +169,7 @@ export async function getTableIndexes(sql: SQL, tableName: string, dbName?: stri
172
169
  if (!indexes[row.INDEX_NAME]) indexes[row.INDEX_NAME] = [];
173
170
  indexes[row.INDEX_NAME].push(row.COLUMN_NAME);
174
171
  }
175
- } else if (IS_PG) {
172
+ } else if (isPG()) {
176
173
  const result = await sql`
177
174
  SELECT indexname, indexdef
178
175
  FROM pg_indexes
@@ -185,7 +182,7 @@ export async function getTableIndexes(sql: SQL, tableName: string, dbName?: stri
185
182
  indexes[row.indexname] = [col];
186
183
  }
187
184
  }
188
- } else if (IS_SQLITE) {
185
+ } else if (isSQLite()) {
189
186
  const list = await sql.unsafe(`PRAGMA index_list(${tableName})`);
190
187
  for (const idx of list) {
191
188
  const info = await sql.unsafe(`PRAGMA index_info(${idx.name})`);
@@ -6,12 +6,10 @@
6
6
  */
7
7
 
8
8
  import { Logger } from '../../lib/logger.js';
9
+ import { IS_PLAN } from './constants.js';
9
10
  import { createTable } from './tableCreate.js';
10
11
  import type { SQL } from 'bun';
11
12
 
12
- // 是否为计划模式(从环境变量读取)
13
- const IS_PLAN = process.argv.includes('--plan');
14
-
15
13
  /**
16
14
  * SQLite 重建表迁移(简化版)
17
15
  *