befly 3.10.18 → 3.10.19

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 (220) hide show
  1. package/README.md +83 -307
  2. package/dist/befly.config.d.ts +7 -0
  3. package/{befly.config.ts → dist/befly.config.js} +10 -33
  4. package/dist/checks/checkApi.d.ts +1 -0
  5. package/{checks/checkApi.ts → dist/checks/checkApi.js} +11 -28
  6. package/dist/checks/checkHook.d.ts +1 -0
  7. package/{checks/checkHook.ts → dist/checks/checkHook.js} +11 -20
  8. package/dist/checks/checkMenu.d.ts +7 -0
  9. package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
  10. package/dist/checks/checkPlugin.d.ts +1 -0
  11. package/{checks/checkPlugin.ts → dist/checks/checkPlugin.js} +11 -20
  12. package/dist/checks/checkTable.d.ts +6 -0
  13. package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
  14. package/dist/configs/presetFields.d.ts +4 -0
  15. package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
  16. package/dist/configs/presetRegexp.d.ts +145 -0
  17. package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
  18. package/dist/hooks/auth.d.ts +5 -0
  19. package/{hooks/auth.ts → dist/hooks/auth.js} +6 -10
  20. package/dist/hooks/cors.d.ts +9 -0
  21. package/{hooks/cors.ts → dist/hooks/cors.js} +3 -13
  22. package/dist/hooks/parser.d.ts +12 -0
  23. package/{hooks/parser.ts → dist/hooks/parser.js} +29 -44
  24. package/dist/hooks/permission.d.ts +12 -0
  25. package/{hooks/permission.ts → dist/hooks/permission.js} +14 -25
  26. package/dist/hooks/validator.d.ts +9 -0
  27. package/{hooks/validator.ts → dist/hooks/validator.js} +7 -14
  28. package/dist/lib/asyncContext.d.ts +21 -0
  29. package/dist/lib/asyncContext.js +27 -0
  30. package/dist/lib/cacheHelper.d.ts +95 -0
  31. package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
  32. package/dist/lib/cacheKeys.d.ts +23 -0
  33. package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
  34. package/dist/lib/cipher.d.ts +153 -0
  35. package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
  36. package/dist/lib/connect.d.ts +91 -0
  37. package/{lib/connect.ts → dist/lib/connect.js} +47 -88
  38. package/dist/lib/dbDialect.d.ts +87 -0
  39. package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
  40. package/dist/lib/dbHelper.d.ts +204 -0
  41. package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +83 -240
  42. package/dist/lib/dbUtils.d.ts +68 -0
  43. package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -125
  44. package/dist/lib/jwt.d.ts +13 -0
  45. package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
  46. package/dist/lib/logger.d.ts +32 -0
  47. package/{lib/logger.ts → dist/lib/logger.js} +202 -279
  48. package/dist/lib/redisHelper.d.ts +185 -0
  49. package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
  50. package/dist/lib/sqlBuilder.d.ts +160 -0
  51. package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
  52. package/dist/lib/sqlCheck.d.ts +23 -0
  53. package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
  54. package/dist/lib/validator.d.ts +45 -0
  55. package/{lib/validator.ts → dist/lib/validator.js} +44 -61
  56. package/dist/loader/loadApis.d.ts +12 -0
  57. package/{loader/loadApis.ts → dist/loader/loadApis.js} +9 -19
  58. package/dist/loader/loadHooks.d.ts +8 -0
  59. package/{loader/loadHooks.ts → dist/loader/loadHooks.js} +7 -21
  60. package/dist/loader/loadPlugins.d.ts +8 -0
  61. package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +10 -22
  62. package/dist/main.d.ts +26 -0
  63. package/{main.ts → dist/main.js} +60 -99
  64. package/dist/paths.d.ts +93 -0
  65. package/{paths.ts → dist/paths.js} +6 -19
  66. package/dist/plugins/cache.d.ts +14 -0
  67. package/{plugins/cache.ts → dist/plugins/cache.js} +5 -12
  68. package/dist/plugins/cipher.d.ts +10 -0
  69. package/{plugins/cipher.ts → dist/plugins/cipher.js} +2 -6
  70. package/dist/plugins/config.d.ts +10 -0
  71. package/dist/plugins/config.js +6 -0
  72. package/dist/plugins/db.d.ts +14 -0
  73. package/{plugins/db.ts → dist/plugins/db.js} +9 -17
  74. package/dist/plugins/jwt.d.ts +10 -0
  75. package/dist/plugins/jwt.js +10 -0
  76. package/dist/plugins/logger.d.ts +28 -0
  77. package/{plugins/logger.ts → dist/plugins/logger.js} +3 -8
  78. package/dist/plugins/redis.d.ts +14 -0
  79. package/{plugins/redis.ts → dist/plugins/redis.js} +7 -12
  80. package/dist/plugins/tool.d.ts +79 -0
  81. package/{plugins/tool.ts → dist/plugins/tool.js} +7 -30
  82. package/dist/router/api.d.ts +14 -0
  83. package/dist/router/api.js +107 -0
  84. package/dist/router/static.d.ts +9 -0
  85. package/{router/static.ts → dist/router/static.js} +20 -34
  86. package/dist/scripts/ensureDist.d.ts +1 -0
  87. package/dist/scripts/ensureDist.js +80 -0
  88. package/dist/sync/syncApi.d.ts +3 -0
  89. package/{sync/syncApi.ts → dist/sync/syncApi.js} +34 -54
  90. package/dist/sync/syncCache.d.ts +2 -0
  91. package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
  92. package/dist/sync/syncDev.d.ts +6 -0
  93. package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
  94. package/dist/sync/syncMenu.d.ts +14 -0
  95. package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
  96. package/dist/sync/syncTable.d.ts +151 -0
  97. package/{sync/syncTable.ts → dist/sync/syncTable.js} +171 -378
  98. package/{types → dist/types}/api.d.ts +8 -47
  99. package/dist/types/api.js +4 -0
  100. package/{types → dist/types}/befly.d.ts +31 -222
  101. package/dist/types/befly.js +4 -0
  102. package/{types → dist/types}/cache.d.ts +7 -15
  103. package/dist/types/cache.js +4 -0
  104. package/dist/types/cipher.d.ts +27 -0
  105. package/dist/types/cipher.js +7 -0
  106. package/{types → dist/types}/common.d.ts +8 -33
  107. package/dist/types/common.js +5 -0
  108. package/{types → dist/types}/context.d.ts +2 -4
  109. package/dist/types/context.js +4 -0
  110. package/{types → dist/types}/crypto.d.ts +0 -3
  111. package/dist/types/crypto.js +4 -0
  112. package/dist/types/database.d.ts +138 -0
  113. package/dist/types/database.js +4 -0
  114. package/dist/types/hook.d.ts +15 -0
  115. package/dist/types/hook.js +6 -0
  116. package/dist/types/jwt.d.ts +75 -0
  117. package/dist/types/jwt.js +4 -0
  118. package/dist/types/logger.d.ts +47 -0
  119. package/dist/types/logger.js +6 -0
  120. package/dist/types/plugin.d.ts +14 -0
  121. package/dist/types/plugin.js +6 -0
  122. package/dist/types/redis.d.ts +71 -0
  123. package/dist/types/redis.js +4 -0
  124. package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
  125. package/dist/types/roleApisCache.js +8 -0
  126. package/dist/types/sync.d.ts +92 -0
  127. package/dist/types/sync.js +4 -0
  128. package/dist/types/table.d.ts +34 -0
  129. package/dist/types/table.js +4 -0
  130. package/dist/types/validate.d.ts +67 -0
  131. package/dist/types/validate.js +4 -0
  132. package/dist/utils/arrayKeysToCamel.d.ts +13 -0
  133. package/{utils/arrayKeysToCamel.ts → dist/utils/arrayKeysToCamel.js} +5 -5
  134. package/dist/utils/calcPerfTime.d.ts +4 -0
  135. package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
  136. package/dist/utils/configTypes.d.ts +1 -0
  137. package/dist/utils/configTypes.js +1 -0
  138. package/dist/utils/convertBigIntFields.d.ts +11 -0
  139. package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
  140. package/dist/utils/cors.d.ts +8 -0
  141. package/{utils/cors.ts → dist/utils/cors.js} +1 -3
  142. package/dist/utils/disableMenusGlob.d.ts +13 -0
  143. package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
  144. package/dist/utils/fieldClear.d.ts +11 -0
  145. package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
  146. package/dist/utils/genShortId.d.ts +10 -0
  147. package/{utils/genShortId.ts → dist/utils/genShortId.js} +1 -1
  148. package/dist/utils/getClientIp.d.ts +6 -0
  149. package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
  150. package/dist/utils/importDefault.d.ts +1 -0
  151. package/dist/utils/importDefault.js +29 -0
  152. package/dist/utils/isDirentDirectory.d.ts +2 -0
  153. package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
  154. package/dist/utils/keysToCamel.d.ts +10 -0
  155. package/{utils/keysToCamel.ts → dist/utils/keysToCamel.js} +4 -5
  156. package/dist/utils/keysToSnake.d.ts +10 -0
  157. package/{utils/keysToSnake.ts → dist/utils/keysToSnake.js} +4 -5
  158. package/dist/utils/loadMenuConfigs.d.ts +5 -0
  159. package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +24 -51
  160. package/dist/utils/pickFields.d.ts +4 -0
  161. package/{utils/pickFields.ts → dist/utils/pickFields.js} +2 -5
  162. package/dist/utils/process.d.ts +24 -0
  163. package/{utils/process.ts → dist/utils/process.js} +2 -18
  164. package/dist/utils/processFields.d.ts +4 -0
  165. package/{utils/processFields.ts → dist/utils/processFields.js} +5 -9
  166. package/dist/utils/regex.d.ts +145 -0
  167. package/{configs/presetRegexp.ts → dist/utils/regex.js} +8 -31
  168. package/dist/utils/response.d.ts +20 -0
  169. package/{utils/response.ts → dist/utils/response.js} +28 -49
  170. package/dist/utils/scanAddons.d.ts +17 -0
  171. package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +6 -40
  172. package/dist/utils/scanConfig.d.ts +26 -0
  173. package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +22 -59
  174. package/dist/utils/scanFiles.d.ts +30 -0
  175. package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +26 -66
  176. package/dist/utils/scanSources.d.ts +10 -0
  177. package/dist/utils/scanSources.js +41 -0
  178. package/dist/utils/sortModules.d.ts +28 -0
  179. package/{utils/sortModules.ts → dist/utils/sortModules.js} +25 -65
  180. package/dist/utils/sqlLog.d.ts +14 -0
  181. package/{utils/sqlLog.ts → dist/utils/sqlLog.js} +2 -14
  182. package/package.json +14 -28
  183. package/.gitignore +0 -0
  184. package/bunfig.toml +0 -3
  185. package/docs/README.md +0 -98
  186. package/docs/api/api.md +0 -1921
  187. package/docs/guide/examples.md +0 -926
  188. package/docs/guide/quickstart.md +0 -354
  189. package/docs/hooks/auth.md +0 -38
  190. package/docs/hooks/cors.md +0 -28
  191. package/docs/hooks/hook.md +0 -838
  192. package/docs/hooks/parser.md +0 -19
  193. package/docs/hooks/rateLimit.md +0 -47
  194. package/docs/infra/redis.md +0 -628
  195. package/docs/plugins/cipher.md +0 -61
  196. package/docs/plugins/database.md +0 -189
  197. package/docs/plugins/plugin.md +0 -986
  198. package/docs/reference/addon.md +0 -510
  199. package/docs/reference/config.md +0 -573
  200. package/docs/reference/logger.md +0 -495
  201. package/docs/reference/sync.md +0 -478
  202. package/docs/reference/table.md +0 -763
  203. package/docs/reference/validator.md +0 -620
  204. package/lib/asyncContext.ts +0 -43
  205. package/plugins/config.ts +0 -13
  206. package/plugins/jwt.ts +0 -15
  207. package/router/api.ts +0 -130
  208. package/tsconfig.json +0 -8
  209. package/types/database.d.ts +0 -541
  210. package/types/hook.d.ts +0 -25
  211. package/types/jwt.d.ts +0 -118
  212. package/types/logger.d.ts +0 -65
  213. package/types/plugin.d.ts +0 -19
  214. package/types/redis.d.ts +0 -83
  215. package/types/sync.d.ts +0 -398
  216. package/types/table.d.ts +0 -216
  217. package/types/validate.d.ts +0 -69
  218. package/utils/configTypes.ts +0 -3
  219. package/utils/importDefault.ts +0 -21
  220. package/utils/scanSources.ts +0 -64
@@ -5,104 +5,20 @@
5
5
  * - 历史上该能力拆分在 packages/core/sync/syncTable/* 多个模块中
6
6
  * - 现在按项目要求,将所有实现合并到本文件(目录 packages/core/sync/syncTable/ 已删除)
7
7
  */
8
-
9
- import type { DbDialectName } from "../lib/dbDialect.ts";
10
- import type { BeflyContext } from "../types/befly.ts";
11
- import type { DbResult, SqlInfo } from "../types/database.ts";
12
- import type { ColumnInfo, FieldChange, IndexInfo, TablePlan } from "../types/sync.ts";
13
- import type { FieldDefinition } from "../types/validate.ts";
14
- import type { ScanFileResult } from "../utils/scanFiles.ts";
15
-
16
8
  import { snakeCase } from "es-toolkit/string";
17
-
18
- import { CacheKeys } from "../lib/cacheKeys.ts";
19
- import { getDialectByName, getSyncTableColumnsInfoQuery, getSyncTableIndexesQuery } from "../lib/dbDialect.ts";
20
- import { Logger } from "../lib/logger.ts";
21
-
22
- type SqlExecutor = {
23
- unsafe<T = any>(sqlStr: string, params?: unknown[]): Promise<DbResult<T, SqlInfo>>;
24
- };
25
-
26
- type MySqlTableExistsRow = { count: number };
27
- type MySqlColumnInfoRow = {
28
- COLUMN_NAME: string;
29
- DATA_TYPE: string;
30
- COLUMN_TYPE: string;
31
- CHARACTER_MAXIMUM_LENGTH: number | null;
32
- IS_NULLABLE: "YES" | "NO";
33
- COLUMN_DEFAULT: any;
34
- COLUMN_COMMENT: string | null;
35
- };
36
-
37
- type PgTableExistsRow = { count: number };
38
- type PgColumnInfoRow = {
39
- column_name: string;
40
- data_type: string;
41
- character_maximum_length: number | null;
42
- is_nullable: string;
43
- column_default: any;
44
- };
45
- type PgColumnCommentRow = {
46
- column_name: string;
47
- column_comment: string;
48
- };
49
-
50
- type MySqlIndexRow = {
51
- INDEX_NAME: string;
52
- COLUMN_NAME: string;
53
- };
54
-
55
- type PgIndexRow = {
56
- indexname: string;
57
- indexdef: string;
58
- };
59
-
60
- type SqliteTableInfoRow = {
61
- name: string;
62
- type: string;
63
- notnull: number;
64
- dflt_value: any;
65
- };
66
-
67
- type SqliteIndexListRow = { name: string };
68
- type SqliteIndexInfoRow = { name: string };
69
-
70
- type DbDialect = DbDialectName;
71
-
72
- /* ========================================================================== */
73
- /* 对外导出面
74
- *
75
- * 约束:本文件仅导出一个函数:syncTable。
76
- * - 生产代码:通过 await syncTable(ctx, items) 执行同步。
77
- * - 测试:通过 syncTable.TestKit 访问纯函数/常量(不再导出零散函数)。
78
- */
79
- /* ========================================================================== */
80
-
81
- /**
82
- * 文件导航(推荐阅读顺序)
83
- * 1) syncTable(ctx, items) 入口(本段下方)
84
- * 2) 版本/常量/方言判断(DB_VERSION_REQUIREMENTS 等)
85
- * 3) 通用 DDL 工具(quote/type/default/ddl/index SQL)
86
- * 4) Runtime I/O(只读元信息:表/列/索引/版本)
87
- * 5) plan/apply(写变更:建表/改表/SQLite 重建)
88
- */
89
-
90
- type SyncTableFn = ((ctx: BeflyContext, items: ScanFileResult[]) => Promise<void>) & {
91
- TestKit: typeof SYNC_TABLE_TEST_KIT;
92
- };
93
-
9
+ import { CacheKeys } from "../lib/cacheKeys.js";
10
+ import { getDialectByName, getSyncTableColumnsInfoQuery, getSyncTableIndexesQuery } from "../lib/dbDialect.js";
11
+ import { Logger } from "../lib/logger.js";
94
12
  /**
95
13
  * 数据库同步命令入口(函数模式)
96
14
  */
97
- export const syncTable = (async (ctx: BeflyContext, items: ScanFileResult[]): Promise<void> => {
15
+ export const syncTable = (async (ctx, items) => {
98
16
  try {
99
17
  // 记录处理过的表名(用于清理缓存)
100
- const processedTables: string[] = [];
101
-
18
+ const processedTables = [];
102
19
  if (!Array.isArray(items)) {
103
20
  throw new Error("syncTable(items) 参数必须是数组");
104
21
  }
105
-
106
22
  if (!ctx) {
107
23
  throw new Error("syncTable(ctx, items) 缺少 ctx");
108
24
  }
@@ -115,7 +31,6 @@ export const syncTable = (async (ctx: BeflyContext, items: ScanFileResult[]): Pr
115
31
  if (!ctx.config) {
116
32
  throw new Error("syncTable(ctx, items) 缺少 ctx.config");
117
33
  }
118
-
119
34
  // DbDialect 归一化(允许值与映射关系):
120
35
  //
121
36
  // | ctx.config.db.type 输入 | 归一化 dbDialect |
@@ -128,39 +43,34 @@ export const syncTable = (async (ctx: BeflyContext, items: ScanFileResult[]): Pr
128
43
  // - 这里的归一化
129
44
  // - ensureDbVersion / runtime I/O / DDL 分支
130
45
  const dbType = String(ctx.config.db?.type || "mysql").toLowerCase();
131
- let dbDialect: DbDialect = "mysql";
46
+ let dbDialect = "mysql";
132
47
  if (dbType === "postgres" || dbType === "postgresql") {
133
48
  dbDialect = "postgresql";
134
- } else if (dbType === "sqlite") {
49
+ }
50
+ else if (dbType === "sqlite") {
135
51
  dbDialect = "sqlite";
136
52
  }
137
-
138
53
  // 检查数据库版本(复用 ctx.db 的现有连接/事务)
139
54
  await ensureDbVersion(dbDialect, ctx.db);
140
-
141
55
  const databaseName = ctx.config.db?.database || "";
142
- const runtime: SyncRuntime = {
56
+ const runtime = {
143
57
  dbDialect: dbDialect,
144
58
  db: ctx.db,
145
59
  dbName: databaseName
146
60
  };
147
-
148
61
  // 处理传入的 tables 数据(来自 scanSources)
149
62
  for (const item of items) {
150
63
  if (!item || item.type !== "table") {
151
64
  continue;
152
65
  }
153
-
154
66
  if (item.source !== "app" && item.source !== "addon" && item.source !== "core") {
155
67
  Logger.warn(`syncTable 跳过未知来源表定义: source=${String(item.source)} fileName=${String(item.fileName)}`);
156
68
  continue;
157
69
  }
158
-
159
70
  // 确定表名:
160
71
  // - addon 表:addon_{addonName}_{fileName}
161
72
  // - app/core 表:{fileName}
162
73
  const baseTableName = snakeCase(item.fileName);
163
-
164
74
  let tableName = baseTableName;
165
75
  if (item.source === "addon") {
166
76
  if (!item.addonName || String(item.addonName).trim() === "") {
@@ -168,45 +78,39 @@ export const syncTable = (async (ctx: BeflyContext, items: ScanFileResult[]): Pr
168
78
  }
169
79
  tableName = `addon_${snakeCase(item.addonName)}_${baseTableName}`;
170
80
  }
171
-
172
81
  const tableDefinition = item.content;
173
82
  if (!tableDefinition || typeof tableDefinition !== "object") {
174
83
  throw new Error(`syncTable 表定义无效: table=${tableName}`);
175
84
  }
176
-
177
85
  // 为字段属性设置默认值:表定义来自 JSON/扫描结果,字段可能缺省。
178
86
  // 缺省会让 diff/DDL 生成出现 undefined vs null 等差异,导致错误的变更判断。
179
87
  for (const fieldDef of Object.values(tableDefinition)) {
180
88
  applyFieldDefaults(fieldDef);
181
89
  }
182
-
183
90
  const existsTable = await tableExistsRuntime(runtime, tableName);
184
-
185
91
  if (existsTable) {
186
- await modifyTableRuntime(runtime, tableName, tableDefinition as any);
187
- } else {
188
- await createTable(runtime, tableName, tableDefinition as any);
92
+ await modifyTableRuntime(runtime, tableName, tableDefinition);
93
+ }
94
+ else {
95
+ await createTable(runtime, tableName, tableDefinition);
189
96
  }
190
-
191
97
  // 记录处理过的表名(用于清理缓存)
192
98
  processedTables.push(tableName);
193
99
  }
194
-
195
100
  // 清理 Redis 缓存(如果有表被处理)
196
101
  if (processedTables.length > 0) {
197
102
  const cacheKeys = processedTables.map((tableName) => CacheKeys.tableColumns(tableName));
198
103
  await ctx.redis.delBatch(cacheKeys);
199
104
  }
200
- } catch (error: any) {
105
+ }
106
+ catch (error) {
201
107
  Logger.error({ err: error }, "数据库同步失败");
202
108
  throw error;
203
109
  }
204
- }) as SyncTableFn;
205
-
110
+ });
206
111
  /* ========================================================================== */
207
112
  /* 版本/常量/运行时方言状态 */
208
113
  /* ========================================================================== */
209
-
210
114
  /**
211
115
  * 数据库版本要求
212
116
  */
@@ -215,8 +119,7 @@ const DB_VERSION_REQUIREMENTS = {
215
119
  POSTGRES_MIN_MAJOR: 17,
216
120
  SQLITE_MIN_VERSION: "3.50.0",
217
121
  SQLITE_MIN_VERSION_NUM: 35000 // 3 * 10000 + 50 * 100
218
- } as const;
219
-
122
+ };
220
123
  /**
221
124
  * 字段变更类型的中文标签映射
222
125
  */
@@ -227,8 +130,7 @@ const CHANGE_TYPE_LABELS = {
227
130
  default: "默认值",
228
131
  nullable: "可空约束",
229
132
  unique: "唯一约束"
230
- } as const;
231
-
133
+ };
232
134
  /**
233
135
  * MySQL 表配置
234
136
  */
@@ -236,24 +138,14 @@ const MYSQL_TABLE_CONFIG = {
236
138
  ENGINE: "InnoDB",
237
139
  CHARSET: "utf8mb4",
238
140
  COLLATE: "utf8mb4_0900_ai_ci"
239
- } as const;
240
-
241
- type SystemFieldMeta = {
242
- name: "id" | "created_at" | "updated_at" | "deleted_at" | "state";
243
- comment: string;
244
- needsIndex: boolean;
245
- mysqlDdl: string;
246
- pgDdl: string;
247
- sqliteDdl: string;
248
141
  };
249
-
250
142
  /**
251
143
  * 系统字段定义:三处会用到
252
144
  * - createTable:建表时追加系统字段列定义
253
145
  * - modifyTable:对已存在的表补齐缺失的系统字段
254
146
  * - SYSTEM_INDEX_FIELDS:从 needsIndex 派生默认系统索引集合
255
147
  */
256
- const SYSTEM_FIELDS: ReadonlyArray<SystemFieldMeta> = [
148
+ const SYSTEM_FIELDS = [
257
149
  {
258
150
  name: "id",
259
151
  comment: "主键ID",
@@ -295,23 +187,19 @@ const SYSTEM_FIELDS: ReadonlyArray<SystemFieldMeta> = [
295
187
  sqliteDdl: "INTEGER NOT NULL DEFAULT 1"
296
188
  }
297
189
  ];
298
-
299
190
  /**
300
191
  * 需要创建索引的系统字段
301
192
  */
302
- const SYSTEM_INDEX_FIELDS: ReadonlyArray<string> = SYSTEM_FIELDS.filter((f) => f.needsIndex).map((f) => f.name);
303
-
304
- const SYSTEM_FIELD_META_MAP: Record<string, SystemFieldMeta> = {};
193
+ const SYSTEM_INDEX_FIELDS = SYSTEM_FIELDS.filter((f) => f.needsIndex).map((f) => f.name);
194
+ const SYSTEM_FIELD_META_MAP = {};
305
195
  for (const f of SYSTEM_FIELDS) {
306
196
  SYSTEM_FIELD_META_MAP[f.name] = f;
307
197
  }
308
-
309
198
  const SYNC_TABLE_TEST_KIT = {
310
199
  DB_VERSION_REQUIREMENTS: DB_VERSION_REQUIREMENTS,
311
200
  CHANGE_TYPE_LABELS: CHANGE_TYPE_LABELS,
312
201
  MYSQL_TABLE_CONFIG: MYSQL_TABLE_CONFIG,
313
202
  SYSTEM_INDEX_FIELDS: SYSTEM_INDEX_FIELDS,
314
-
315
203
  getTypeMapping: getTypeMapping,
316
204
  quoteIdentifier: quoteIdentifier,
317
205
  escapeComment: escapeComment,
@@ -326,12 +214,10 @@ const SYNC_TABLE_TEST_KIT = {
326
214
  generateDDLClause: generateDDLClause,
327
215
  isCompatibleTypeChange: isCompatibleTypeChange,
328
216
  compareFieldDefinition: compareFieldDefinition,
329
-
330
217
  tableExistsRuntime: tableExistsRuntime,
331
218
  getTableColumnsRuntime: getTableColumnsRuntime,
332
219
  getTableIndexesRuntime: getTableIndexesRuntime,
333
-
334
- createRuntime: (dbDialect: DbDialect, db: SqlExecutor, dbName: string = ""): SyncRuntime => {
220
+ createRuntime: (dbDialect, db, dbName = "") => {
335
221
  return {
336
222
  dbDialect: dbDialect,
337
223
  db: db,
@@ -339,10 +225,8 @@ const SYNC_TABLE_TEST_KIT = {
339
225
  };
340
226
  }
341
227
  };
342
-
343
228
  // 测试能力挂载(避免导出零散函数,同时确保运行时存在)
344
229
  syncTable.TestKit = SYNC_TABLE_TEST_KIT;
345
-
346
230
  // 防御性:避免运行时被误覆盖(只读),但仍保持可枚举/可访问。
347
231
  Object.defineProperty(syncTable, "TestKit", {
348
232
  value: SYNC_TABLE_TEST_KIT,
@@ -350,11 +234,10 @@ Object.defineProperty(syncTable, "TestKit", {
350
234
  enumerable: true,
351
235
  configurable: false
352
236
  });
353
-
354
237
  /**
355
238
  * 获取字段类型映射(根据当前数据库类型)
356
239
  */
357
- function getTypeMapping(dbDialect: DbDialect): Record<string, string> {
240
+ function getTypeMapping(dbDialect) {
358
241
  return {
359
242
  number: dbDialect === "sqlite" ? "INTEGER" : dbDialect === "postgresql" ? "BIGINT" : "BIGINT",
360
243
  string: dbDialect === "sqlite" ? "TEXT" : dbDialect === "postgresql" ? "character varying" : "VARCHAR",
@@ -365,32 +248,27 @@ function getTypeMapping(dbDialect: DbDialect): Record<string, string> {
365
248
  array_number_text: dbDialect === "mysql" ? "MEDIUMTEXT" : "TEXT"
366
249
  };
367
250
  }
368
-
369
251
  /* ========================================================================== */
370
252
  /* 通用工具与 DDL 片段生成 */
371
253
  /* ========================================================================== */
372
-
373
254
  /**
374
255
  * 根据数据库类型引用标识符
375
256
  */
376
- function quoteIdentifier(dbDialect: DbDialect, identifier: string): string {
257
+ function quoteIdentifier(dbDialect, identifier) {
377
258
  return getDialectByName(dbDialect).quoteIdent(identifier);
378
259
  }
379
-
380
260
  /**
381
261
  * 转义 SQL 注释中的双引号
382
262
  */
383
- function escapeComment(str: string): string {
263
+ function escapeComment(str) {
384
264
  return String(str).replace(/"/g, '\\"');
385
265
  }
386
-
387
266
  // 注意:这里刻意不封装“logFieldChange/formatFieldList”之类的一次性工具函数,
388
267
  // 以减少抽象层级(按项目要求:能直写就直写)。
389
-
390
268
  /**
391
269
  * 为字段定义应用默认值
392
270
  */
393
- function applyFieldDefaults(fieldDef: any): void {
271
+ function applyFieldDefaults(fieldDef) {
394
272
  fieldDef.detail = fieldDef.detail ?? "";
395
273
  fieldDef.min = fieldDef.min ?? 0;
396
274
  fieldDef.max = fieldDef.max ?? (fieldDef.type === "number" ? Number.MAX_SAFE_INTEGER : 100);
@@ -401,18 +279,16 @@ function applyFieldDefaults(fieldDef: any): void {
401
279
  fieldDef.unsigned = fieldDef.unsigned ?? true;
402
280
  fieldDef.regexp = fieldDef.regexp ?? null;
403
281
  }
404
-
405
282
  /**
406
283
  * 判断是否为字符串或数组类型(需要长度参数)
407
284
  */
408
- function isStringOrArrayType(fieldType: string): boolean {
285
+ function isStringOrArrayType(fieldType) {
409
286
  return fieldType === "string" || fieldType === "array_string" || fieldType === "array_number_string";
410
287
  }
411
-
412
288
  /**
413
289
  * 获取 SQL 数据类型
414
290
  */
415
- function getSqlType(dbDialect: DbDialect, fieldType: string, fieldMax: number | null, unsigned: boolean = false): string {
291
+ function getSqlType(dbDialect, fieldType, fieldMax, unsigned = false) {
416
292
  const typeMapping = getTypeMapping(dbDialect);
417
293
  if (isStringOrArrayType(fieldType)) {
418
294
  return `${typeMapping[fieldType]}(${fieldMax})`;
@@ -423,15 +299,13 @@ function getSqlType(dbDialect: DbDialect, fieldType: string, fieldMax: number |
423
299
  }
424
300
  return baseType;
425
301
  }
426
-
427
302
  /**
428
303
  * 处理默认值:将 null 或 'null' 字符串转换为对应类型的默认值
429
304
  */
430
- function resolveDefaultValue(fieldDefault: any, fieldType: string): any {
305
+ function resolveDefaultValue(fieldDefault, fieldType) {
431
306
  if (fieldDefault !== null && fieldDefault !== "null") {
432
307
  return fieldDefault;
433
308
  }
434
-
435
309
  switch (fieldType) {
436
310
  case "number":
437
311
  return 0;
@@ -448,31 +322,28 @@ function resolveDefaultValue(fieldDefault: any, fieldType: string): any {
448
322
  return fieldDefault;
449
323
  }
450
324
  }
451
-
452
325
  /**
453
326
  * 生成 SQL DEFAULT 子句
454
327
  */
455
- function generateDefaultSql(actualDefault: any, fieldType: string): string {
328
+ function generateDefaultSql(actualDefault, fieldType) {
456
329
  if (fieldType === "text" || fieldType === "array_text" || actualDefault === "null") {
457
330
  return "";
458
331
  }
459
-
460
332
  if (fieldType === "number" || fieldType === "string" || fieldType === "array_string" || fieldType === "array_number_string") {
461
333
  if (typeof actualDefault === "number" && !Number.isNaN(actualDefault)) {
462
334
  return ` DEFAULT ${actualDefault}`;
463
- } else {
335
+ }
336
+ else {
464
337
  const escaped = String(actualDefault).replace(/'/g, "''");
465
338
  return ` DEFAULT '${escaped}'`;
466
339
  }
467
340
  }
468
-
469
341
  return "";
470
342
  }
471
-
472
343
  /**
473
344
  * 构建索引操作 SQL(统一使用在线策略)
474
345
  */
475
- function buildIndexSQL(dbDialect: DbDialect, tableName: string, indexName: string, fieldName: string, action: "create" | "drop"): string {
346
+ function buildIndexSQL(dbDialect, tableName, indexName, fieldName, action) {
476
347
  // 说明(策略取舍):
477
348
  // - MySQL:通过 ALTER TABLE 在线添加/删除索引;配合 ALGORITHM/LOCK 以降低阻塞。
478
349
  // - PostgreSQL:CREATE/DROP INDEX CONCURRENTLY 尽量减少锁表(代价是执行更慢/有并发限制)。
@@ -480,123 +351,108 @@ function buildIndexSQL(dbDialect: DbDialect, tableName: string, indexName: strin
480
351
  const tableQuoted = quoteIdentifier(dbDialect, tableName);
481
352
  const indexQuoted = quoteIdentifier(dbDialect, indexName);
482
353
  const fieldQuoted = quoteIdentifier(dbDialect, fieldName);
483
-
484
354
  if (dbDialect === "mysql") {
485
- const parts: string[] = [];
355
+ const parts = [];
486
356
  if (action === "create") {
487
357
  parts.push(`ADD INDEX ${indexQuoted} (${fieldQuoted})`);
488
- } else {
358
+ }
359
+ else {
489
360
  parts.push(`DROP INDEX ${indexQuoted}`);
490
361
  }
491
362
  return `ALTER TABLE ${tableQuoted} ALGORITHM=INPLACE, LOCK=NONE, ${parts.join(", ")}`;
492
363
  }
493
-
494
364
  if (dbDialect === "postgresql") {
495
365
  if (action === "create") {
496
366
  return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${indexQuoted} ON ${tableQuoted}(${fieldQuoted})`;
497
367
  }
498
368
  return `DROP INDEX CONCURRENTLY IF EXISTS ${indexQuoted}`;
499
369
  }
500
-
501
370
  if (action === "create") {
502
371
  return `CREATE INDEX IF NOT EXISTS ${indexQuoted} ON ${tableQuoted}(${fieldQuoted})`;
503
372
  }
504
373
  return `DROP INDEX IF EXISTS ${indexQuoted}`;
505
374
  }
506
-
507
375
  /**
508
376
  * 获取单个系统字段的列定义(用于 ADD COLUMN 或 CREATE TABLE)
509
377
  */
510
- function getSystemColumnDef(dbDialect: DbDialect, fieldName: string): string | null {
378
+ function getSystemColumnDef(dbDialect, fieldName) {
511
379
  const meta = SYSTEM_FIELD_META_MAP[fieldName];
512
- if (!meta) return null;
513
-
380
+ if (!meta)
381
+ return null;
514
382
  const colQuoted = quoteIdentifier(dbDialect, meta.name);
515
383
  if (dbDialect === "mysql") {
516
384
  return `${colQuoted} ${meta.mysqlDdl} COMMENT "${escapeComment(meta.comment)}"`;
517
385
  }
518
-
519
386
  if (dbDialect === "postgresql") {
520
387
  return `${colQuoted} ${meta.pgDdl}`;
521
388
  }
522
-
523
389
  return `${colQuoted} ${meta.sqliteDdl}`;
524
390
  }
525
-
526
391
  /**
527
392
  * 构建系统字段列定义
528
393
  */
529
- function buildSystemColumnDefs(dbDialect: DbDialect): string[] {
530
- const defs: string[] = [];
394
+ function buildSystemColumnDefs(dbDialect) {
395
+ const defs = [];
531
396
  for (const f of SYSTEM_FIELDS) {
532
397
  const d = getSystemColumnDef(dbDialect, f.name);
533
- if (d) defs.push(d);
398
+ if (d)
399
+ defs.push(d);
534
400
  }
535
401
  return defs;
536
402
  }
537
-
538
403
  /**
539
404
  * 构建业务字段列定义
540
405
  */
541
- function buildBusinessColumnDefs(dbDialect: DbDialect, fields: Record<string, FieldDefinition>): string[] {
542
- const colDefs: string[] = [];
543
-
406
+ function buildBusinessColumnDefs(dbDialect, fields) {
407
+ const colDefs = [];
544
408
  for (const [fieldKey, fieldDef] of Object.entries(fields)) {
545
409
  const dbFieldName = snakeCase(fieldKey);
546
410
  const colQuoted = quoteIdentifier(dbDialect, dbFieldName);
547
-
548
411
  const sqlType = getSqlType(dbDialect, fieldDef.type, fieldDef.max, fieldDef.unsigned);
549
-
550
412
  const actualDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
551
413
  const defaultSql = generateDefaultSql(actualDefault, fieldDef.type);
552
-
553
414
  const uniqueSql = fieldDef.unique ? " UNIQUE" : "";
554
415
  const nullableSql = fieldDef.nullable ? " NULL" : " NOT NULL";
555
-
556
416
  if (dbDialect === "mysql") {
557
417
  colDefs.push(`${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(fieldDef.name)}"`);
558
- } else {
418
+ }
419
+ else {
559
420
  colDefs.push(`${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`);
560
421
  }
561
422
  }
562
-
563
423
  return colDefs;
564
424
  }
565
-
566
425
  /**
567
426
  * 生成字段 DDL 子句(不含 ALTER TABLE 前缀)
568
427
  */
569
- function generateDDLClause(dbDialect: DbDialect, fieldKey: string, fieldDef: FieldDefinition, isAdd: boolean = false): string {
428
+ function generateDDLClause(dbDialect, fieldKey, fieldDef, isAdd = false) {
570
429
  // 说明(策略取舍):
571
430
  // - MySQL:ADD/MODIFY 一条子句内可同时表达类型/可空/默认值/注释(同步成本低)。
572
431
  // - PostgreSQL:modify 场景这里仅生成 TYPE 变更;默认值/注释等由其他子句或 commentActions 处理。
573
432
  // - SQLite:不支持标准化的 MODIFY COLUMN,这里仅提供 ADD COLUMN;复杂变更通过 rebuildSqliteTable 完成。
574
433
  const dbFieldName = snakeCase(fieldKey);
575
434
  const colQuoted = quoteIdentifier(dbDialect, dbFieldName);
576
-
577
435
  const sqlType = getSqlType(dbDialect, fieldDef.type, fieldDef.max, fieldDef.unsigned);
578
-
579
436
  const actualDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
580
437
  const defaultSql = generateDefaultSql(actualDefault, fieldDef.type);
581
-
582
438
  const uniqueSql = fieldDef.unique ? " UNIQUE" : "";
583
439
  const nullableSql = fieldDef.nullable ? " NULL" : " NOT NULL";
584
-
585
440
  if (dbDialect === "mysql") {
586
441
  return `${isAdd ? "ADD COLUMN" : "MODIFY COLUMN"} ${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql} COMMENT "${escapeComment(fieldDef.name)}"`;
587
442
  }
588
443
  if (dbDialect === "postgresql") {
589
- if (isAdd) return `ADD COLUMN IF NOT EXISTS ${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`;
444
+ if (isAdd)
445
+ return `ADD COLUMN IF NOT EXISTS ${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`;
590
446
  return `ALTER COLUMN ${colQuoted} TYPE ${sqlType}`;
591
447
  }
592
- if (isAdd) return `ADD COLUMN IF NOT EXISTS ${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`;
448
+ if (isAdd)
449
+ return `ADD COLUMN IF NOT EXISTS ${colQuoted} ${sqlType}${uniqueSql}${nullableSql}${defaultSql}`;
593
450
  return "";
594
451
  }
595
-
596
452
  /**
597
453
  * 安全执行 DDL 语句(MySQL 降级策略)
598
454
  */
599
- async function executeDDLSafely(db: SqlExecutor, stmt: string): Promise<boolean> {
455
+ async function executeDDLSafely(db, stmt) {
600
456
  // MySQL DDL 兼容性/可用性兜底:
601
457
  // - 优先执行原语句(通常含 ALGORITHM=INSTANT)。
602
458
  // - 若 INSTANT 不可用(版本/表结构限制),降级为 INPLACE 再试。
@@ -604,16 +460,17 @@ async function executeDDLSafely(db: SqlExecutor, stmt: string): Promise<boolean>
604
460
  try {
605
461
  await db.unsafe(stmt);
606
462
  return true;
607
- } catch (error: any) {
463
+ }
464
+ catch (error) {
608
465
  if (stmt.includes("ALGORITHM=INSTANT")) {
609
466
  const inplaceSql = stmt.replace(/ALGORITHM=INSTANT/g, "ALGORITHM=INPLACE");
610
467
  try {
611
468
  await db.unsafe(inplaceSql);
612
469
  return true;
613
- } catch {
470
+ }
471
+ catch {
614
472
  let traditionSql = stmt;
615
473
  traditionSql = traditionSql.replace(/\bALGORITHM\s*=\s*(INPLACE|INSTANT)\b\s*,?\s*/g, "").replace(/\bLOCK\s*=\s*(NONE|SHARED|EXCLUSIVE)\b\s*,?\s*/g, "");
616
-
617
474
  traditionSql = traditionSql
618
475
  .replace(/,\s*,/g, ", ")
619
476
  .replace(/,\s*$/g, "")
@@ -622,16 +479,16 @@ async function executeDDLSafely(db: SqlExecutor, stmt: string): Promise<boolean>
622
479
  await db.unsafe(traditionSql);
623
480
  return true;
624
481
  }
625
- } else {
482
+ }
483
+ else {
626
484
  throw error;
627
485
  }
628
486
  }
629
487
  }
630
-
631
488
  /**
632
489
  * 判断是否为兼容的类型变更(宽化型变更,无数据丢失风险)
633
490
  */
634
- function isCompatibleTypeChange(currentType: string, newType: string): boolean {
491
+ function isCompatibleTypeChange(currentType, newType) {
635
492
  // 说明:该函数用于“自动同步”里的安全阈值判断。
636
493
  // - 允许:宽化型变更(不收缩、不改变语义大类),例如:
637
494
  // - INT -> BIGINT(或 tinyint/smallint/mediumint -> 更宽的整型)
@@ -640,50 +497,28 @@ function isCompatibleTypeChange(currentType: string, newType: string): boolean {
640
497
  // - 禁止:收缩型变更(BIGINT -> INT、TEXT -> VARCHAR)以及跨大类变更(需人工评估/迁移)。
641
498
  const c = String(currentType || "").toLowerCase();
642
499
  const n = String(newType || "").toLowerCase();
643
-
644
- if (c === n) return false;
645
-
500
+ if (c === n)
501
+ return false;
646
502
  const cBase = c
647
503
  .replace(/\s*unsigned/gi, "")
648
504
  .replace(/\([^)]*\)/g, "")
649
505
  .trim();
650
-
651
506
  const nBase = n
652
507
  .replace(/\s*unsigned/gi, "")
653
508
  .replace(/\([^)]*\)/g, "")
654
509
  .trim();
655
-
656
510
  const intTypes = ["tinyint", "smallint", "mediumint", "int", "integer", "bigint"];
657
511
  const cIntIdx = intTypes.indexOf(cBase);
658
512
  const nIntIdx = intTypes.indexOf(nBase);
659
513
  if (cIntIdx !== -1 && nIntIdx !== -1 && nIntIdx > cIntIdx) {
660
514
  return true;
661
515
  }
662
-
663
- if (cBase === "varchar" && (nBase === "text" || nBase === "mediumtext" || nBase === "longtext")) return true;
664
- if (cBase === "character varying" && nBase === "text") return true;
665
-
516
+ if (cBase === "varchar" && (nBase === "text" || nBase === "mediumtext" || nBase === "longtext"))
517
+ return true;
518
+ if (cBase === "character varying" && nBase === "text")
519
+ return true;
666
520
  return false;
667
521
  }
668
-
669
- type SyncRuntime = {
670
- /**
671
- * 当前数据库方言(mysql/postgresql/sqlite),决定 SQL 片段与元信息查询方式。
672
- * 约束:必须与 ctx.config.db.type 一致(经归一化)。
673
- */
674
- dbDialect: DbDialect;
675
- /**
676
- * SQL 执行器:必须复用 ctx.db。
677
- * 约束:syncTable 内部禁止新建 DB 连接/事务;runtime 仅保存引用,不拥有生命周期。
678
- */
679
- db: SqlExecutor;
680
- /**
681
- * 数据库名:主要用于 MySQL information_schema 查询。
682
- * 约束:PG/SQLite 可以传空字符串;不要在非 MySQL 方言依赖该值。
683
- */
684
- dbName: string;
685
- };
686
-
687
522
  /* ========================================================================== */
688
523
  /* runtime I/O(只读:读库/元信息查询)
689
524
  *
@@ -693,43 +528,41 @@ type SyncRuntime = {
693
528
  * - 对外不再保留 dbDialect/db/dbName 形式的 wrapper;统一使用 runtime 形态(更直写)。
694
529
  */
695
530
  /* ========================================================================== */
696
-
697
531
  // ---------------------------------------------------------------------------
698
532
  // 读:表是否存在
699
533
  // ---------------------------------------------------------------------------
700
-
701
- async function tableExistsRuntime(runtime: SyncRuntime, tableName: string): Promise<boolean> {
702
- if (!runtime.db) throw new Error("SQL 执行器未初始化");
534
+ async function tableExistsRuntime(runtime, tableName) {
535
+ if (!runtime.db)
536
+ throw new Error("SQL 执行器未初始化");
703
537
  try {
704
538
  // 统一交由方言层构造 SQL;syncTable 仅决定“要查哪个 schema/db”。
705
539
  // - MySQL:传 runtime.dbName(information_schema.table_schema)
706
540
  // - PostgreSQL:固定 public(项目约定)
707
541
  // - SQLite:忽略 schema
708
- let schema: string | undefined = undefined;
542
+ let schema = undefined;
709
543
  if (runtime.dbDialect === "mysql") {
710
544
  schema = runtime.dbName;
711
- } else if (runtime.dbDialect === "postgresql") {
545
+ }
546
+ else if (runtime.dbDialect === "postgresql") {
712
547
  schema = "public";
713
548
  }
714
-
715
549
  const q = getDialectByName(runtime.dbDialect).tableExistsQuery(tableName, schema);
716
- const res = await runtime.db.unsafe<MySqlTableExistsRow[] | PgTableExistsRow[]>(q.sql, q.params);
550
+ const res = await runtime.db.unsafe(q.sql, q.params);
717
551
  return (res.data?.[0]?.count || 0) > 0;
718
- } catch (error: any) {
552
+ }
553
+ catch (error) {
719
554
  const errMsg = String(error?.message || error);
720
- const outErr: any = new Error(`runtime I/O 失败: op=tableExists table=${tableName} err=${errMsg}`);
721
- if (error?.sqlInfo) outErr.sqlInfo = error.sqlInfo;
555
+ const outErr = new Error(`runtime I/O 失败: op=tableExists table=${tableName} err=${errMsg}`);
556
+ if (error?.sqlInfo)
557
+ outErr.sqlInfo = error.sqlInfo;
722
558
  throw outErr;
723
559
  }
724
560
  }
725
-
726
561
  // ---------------------------------------------------------------------------
727
562
  // 读:列信息
728
563
  // ---------------------------------------------------------------------------
729
-
730
- async function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string): Promise<{ [key: string]: ColumnInfo }> {
731
- const columns: { [key: string]: ColumnInfo } = {};
732
-
564
+ async function getTableColumnsRuntime(runtime, tableName) {
565
+ const columns = {};
733
566
  try {
734
567
  // 方言差异说明:
735
568
  // - MySQL:information_schema.columns 最完整,包含 COLUMN_TYPE 与 COLUMN_COMMENT。
@@ -737,10 +570,9 @@ async function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string):
737
570
  // - SQLite:PRAGMA table_info 仅提供 type/notnull/default 等有限信息,无列注释。
738
571
  if (runtime.dbDialect === "mysql") {
739
572
  const q = getSyncTableColumnsInfoQuery({ dialect: "mysql", table: tableName, dbName: runtime.dbName });
740
- const result = await runtime.db.unsafe<MySqlColumnInfoRow[]>(q.columns.sql, q.columns.params);
573
+ const result = await runtime.db.unsafe(q.columns.sql, q.columns.params);
741
574
  for (const row of result.data) {
742
575
  const defaultValue = row.COLUMN_DEFAULT;
743
-
744
576
  columns[row.COLUMN_NAME] = {
745
577
  type: row.DATA_TYPE,
746
578
  columnType: row.COLUMN_TYPE,
@@ -751,13 +583,14 @@ async function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string):
751
583
  comment: row.COLUMN_COMMENT
752
584
  };
753
585
  }
754
- } else if (runtime.dbDialect === "postgresql") {
586
+ }
587
+ else if (runtime.dbDialect === "postgresql") {
755
588
  const q = getSyncTableColumnsInfoQuery({ dialect: "postgresql", table: tableName, dbName: runtime.dbName });
756
- const result = await runtime.db.unsafe<PgColumnInfoRow[]>(q.columns.sql, q.columns.params);
757
- const comments = q.comments ? (await runtime.db.unsafe<PgColumnCommentRow[]>(q.comments.sql, q.comments.params)).data : [];
758
- const commentMap: { [key: string]: string } = {};
759
- for (const r of comments) commentMap[r.column_name] = r.column_comment;
760
-
589
+ const result = await runtime.db.unsafe(q.columns.sql, q.columns.params);
590
+ const comments = q.comments ? (await runtime.db.unsafe(q.comments.sql, q.comments.params)).data : [];
591
+ const commentMap = {};
592
+ for (const r of comments)
593
+ commentMap[r.column_name] = r.column_comment;
761
594
  for (const row of result.data) {
762
595
  columns[row.column_name] = {
763
596
  type: row.data_type,
@@ -769,9 +602,10 @@ async function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string):
769
602
  comment: commentMap[row.column_name] ?? null
770
603
  };
771
604
  }
772
- } else if (runtime.dbDialect === "sqlite") {
605
+ }
606
+ else if (runtime.dbDialect === "sqlite") {
773
607
  const q = getSyncTableColumnsInfoQuery({ dialect: "sqlite", table: tableName, dbName: runtime.dbName });
774
- const result = await runtime.db.unsafe<SqliteTableInfoRow[]>(q.columns.sql, q.columns.params);
608
+ const result = await runtime.db.unsafe(q.columns.sql, q.columns.params);
775
609
  for (const row of result.data) {
776
610
  let baseType = String(row.type || "").toUpperCase();
777
611
  let max = null;
@@ -791,23 +625,21 @@ async function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string):
791
625
  };
792
626
  }
793
627
  }
794
-
795
628
  return columns;
796
- } catch (error: any) {
629
+ }
630
+ catch (error) {
797
631
  const errMsg = String(error?.message || error);
798
- const outErr: any = new Error(`runtime I/O 失败: op=getTableColumns table=${tableName} err=${errMsg}`);
799
- if (error?.sqlInfo) outErr.sqlInfo = error.sqlInfo;
632
+ const outErr = new Error(`runtime I/O 失败: op=getTableColumns table=${tableName} err=${errMsg}`);
633
+ if (error?.sqlInfo)
634
+ outErr.sqlInfo = error.sqlInfo;
800
635
  throw outErr;
801
636
  }
802
637
  }
803
-
804
638
  // ---------------------------------------------------------------------------
805
639
  // 读:索引信息(单列索引)
806
640
  // ---------------------------------------------------------------------------
807
-
808
- async function getTableIndexesRuntime(runtime: SyncRuntime, tableName: string): Promise<IndexInfo> {
809
- const indexes: IndexInfo = {};
810
-
641
+ async function getTableIndexesRuntime(runtime, tableName) {
642
+ const indexes = {};
811
643
  try {
812
644
  // 方言差异说明:
813
645
  // - MySQL:information_schema.statistics 直接给出 index -> column 映射。
@@ -815,14 +647,16 @@ async function getTableIndexesRuntime(runtime: SyncRuntime, tableName: string):
815
647
  // - SQLite:PRAGMA index_list + index_info;同样仅收集单列索引,避免多列索引误判。
816
648
  if (runtime.dbDialect === "mysql") {
817
649
  const q = getSyncTableIndexesQuery({ dialect: "mysql", table: tableName, dbName: runtime.dbName });
818
- const result = await runtime.db.unsafe<MySqlIndexRow[]>(q.sql, q.params);
650
+ const result = await runtime.db.unsafe(q.sql, q.params);
819
651
  for (const row of result.data) {
820
- if (!indexes[row.INDEX_NAME]) indexes[row.INDEX_NAME] = [];
652
+ if (!indexes[row.INDEX_NAME])
653
+ indexes[row.INDEX_NAME] = [];
821
654
  indexes[row.INDEX_NAME].push(row.COLUMN_NAME);
822
655
  }
823
- } else if (runtime.dbDialect === "postgresql") {
656
+ }
657
+ else if (runtime.dbDialect === "postgresql") {
824
658
  const q = getSyncTableIndexesQuery({ dialect: "postgresql", table: tableName, dbName: runtime.dbName });
825
- const result = await runtime.db.unsafe<PgIndexRow[]>(q.sql, q.params);
659
+ const result = await runtime.db.unsafe(q.sql, q.params);
826
660
  for (const row of result.data) {
827
661
  const m = /\(([^)]+)\)/.exec(row.indexdef);
828
662
  if (m) {
@@ -830,38 +664,39 @@ async function getTableIndexesRuntime(runtime: SyncRuntime, tableName: string):
830
664
  indexes[row.indexname] = [col];
831
665
  }
832
666
  }
833
- } else if (runtime.dbDialect === "sqlite") {
667
+ }
668
+ else if (runtime.dbDialect === "sqlite") {
834
669
  const quotedTable = quoteIdentifier("sqlite", tableName);
835
- const list = await runtime.db.unsafe<SqliteIndexListRow[]>(`PRAGMA index_list(${quotedTable})`);
670
+ const list = await runtime.db.unsafe(`PRAGMA index_list(${quotedTable})`);
836
671
  for (const idx of list.data) {
837
672
  const quotedIndex = quoteIdentifier("sqlite", idx.name);
838
- const info = await runtime.db.unsafe<SqliteIndexInfoRow[]>(`PRAGMA index_info(${quotedIndex})`);
673
+ const info = await runtime.db.unsafe(`PRAGMA index_info(${quotedIndex})`);
839
674
  const cols = info.data.map((r) => r.name);
840
- if (cols.length === 1) indexes[idx.name] = cols;
675
+ if (cols.length === 1)
676
+ indexes[idx.name] = cols;
841
677
  }
842
678
  }
843
-
844
679
  return indexes;
845
- } catch (error: any) {
680
+ }
681
+ catch (error) {
846
682
  const errMsg = String(error?.message || error);
847
- const outErr: any = new Error(`runtime I/O 失败: op=getTableIndexes table=${tableName} err=${errMsg}`);
848
- if (error?.sqlInfo) outErr.sqlInfo = error.sqlInfo;
683
+ const outErr = new Error(`runtime I/O 失败: op=getTableIndexes table=${tableName} err=${errMsg}`);
684
+ if (error?.sqlInfo)
685
+ outErr.sqlInfo = error.sqlInfo;
849
686
  throw outErr;
850
687
  }
851
688
  }
852
-
853
689
  // ---------------------------------------------------------------------------
854
690
  // 读:数据库版本
855
691
  // ---------------------------------------------------------------------------
856
-
857
692
  /**
858
693
  * 数据库版本检查(按方言)
859
694
  */
860
- async function ensureDbVersion(dbDialect: DbDialect, db: SqlExecutor): Promise<void> {
861
- if (!db) throw new Error("SQL 执行器未初始化");
862
-
695
+ async function ensureDbVersion(dbDialect, db) {
696
+ if (!db)
697
+ throw new Error("SQL 执行器未初始化");
863
698
  if (dbDialect === "mysql") {
864
- const r = await db.unsafe<Array<{ version: string }>>("SELECT VERSION() AS version");
699
+ const r = await db.unsafe("SELECT VERSION() AS version");
865
700
  if (!r.data || r.data.length === 0 || !r.data[0]?.version) {
866
701
  throw new Error("无法获取 MySQL 版本信息");
867
702
  }
@@ -872,9 +707,8 @@ async function ensureDbVersion(dbDialect: DbDialect, db: SqlExecutor): Promise<v
872
707
  }
873
708
  return;
874
709
  }
875
-
876
710
  if (dbDialect === "postgresql") {
877
- const r = await db.unsafe<Array<{ version: string }>>("SELECT version() AS version");
711
+ const r = await db.unsafe("SELECT version() AS version");
878
712
  if (!r.data || r.data.length === 0 || !r.data[0]?.version) {
879
713
  throw new Error("无法获取 PostgreSQL 版本信息");
880
714
  }
@@ -886,9 +720,8 @@ async function ensureDbVersion(dbDialect: DbDialect, db: SqlExecutor): Promise<v
886
720
  }
887
721
  return;
888
722
  }
889
-
890
723
  if (dbDialect === "sqlite") {
891
- const r = await db.unsafe<Array<{ version: string }>>("SELECT sqlite_version() AS version");
724
+ const r = await db.unsafe("SELECT sqlite_version() AS version");
892
725
  if (!r.data || r.data.length === 0 || !r.data[0]?.version) {
893
726
  throw new Error("无法获取 SQLite 版本信息");
894
727
  }
@@ -903,13 +736,11 @@ async function ensureDbVersion(dbDialect: DbDialect, db: SqlExecutor): Promise<v
903
736
  return;
904
737
  }
905
738
  }
906
-
907
739
  /**
908
740
  * 比较字段定义变化
909
741
  */
910
- function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo, fieldDef: FieldDefinition): FieldChange[] {
911
- const changes: FieldChange[] = [];
912
-
742
+ function compareFieldDefinition(dbDialect, existingColumn, fieldDef) {
743
+ const changes = [];
913
744
  // SQLite 元信息能力较弱:
914
745
  // - 列注释:sqlite 无 information_schema 注释,PRAGMA table_info 也不提供 comment
915
746
  // - 字符串长度:sqlite 类型系统宽松,长度/类型信息不稳定(易产生误报)
@@ -923,7 +754,6 @@ function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo
923
754
  });
924
755
  }
925
756
  }
926
-
927
757
  if (dbDialect !== "sqlite") {
928
758
  const currentComment = existingColumn.comment || "";
929
759
  if (currentComment !== fieldDef.name) {
@@ -934,11 +764,9 @@ function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo
934
764
  });
935
765
  }
936
766
  }
937
-
938
767
  const typeMapping = getTypeMapping(dbDialect);
939
768
  const expectedType = typeMapping[fieldDef.type].toLowerCase();
940
769
  const currentType = existingColumn.type.toLowerCase();
941
-
942
770
  if (currentType !== expectedType) {
943
771
  changes.push({
944
772
  type: "datatype",
@@ -946,7 +774,6 @@ function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo
946
774
  expected: expectedType
947
775
  });
948
776
  }
949
-
950
777
  const expectedNullable = fieldDef.nullable;
951
778
  if (existingColumn.nullable !== expectedNullable) {
952
779
  changes.push({
@@ -955,7 +782,6 @@ function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo
955
782
  expected: expectedNullable
956
783
  });
957
784
  }
958
-
959
785
  const expectedDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
960
786
  if (String(existingColumn.defaultValue) !== String(expectedDefault)) {
961
787
  changes.push({
@@ -964,18 +790,15 @@ function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo
964
790
  expected: expectedDefault
965
791
  });
966
792
  }
967
-
968
793
  return changes;
969
794
  }
970
-
971
795
  /* ========================================================================== */
972
796
  /* plan/apply & 建表/改表(核心同步逻辑) */
973
797
  /* ========================================================================== */
974
-
975
798
  /**
976
799
  * SQLite 重建表迁移(简化版)
977
800
  */
978
- async function rebuildSqliteTable(runtime: SyncRuntime, tableName: string, fields: Record<string, FieldDefinition>): Promise<void> {
801
+ async function rebuildSqliteTable(runtime, tableName, fields) {
979
802
  // 说明:SQLite ALTER TABLE 能力有限(尤其是修改列类型/默认值/约束)。
980
803
  // 策略:创建临时表 -> 复制“交集列”数据 -> 删除旧表 -> 临时表改名。
981
804
  // - 只复制 targetCols 与 existingCols 的交集,避免因新增列/删除列导致 INSERT 失败。
@@ -983,66 +806,66 @@ async function rebuildSqliteTable(runtime: SyncRuntime, tableName: string, field
983
806
  if (runtime.dbDialect !== "sqlite") {
984
807
  throw new Error(`rebuildSqliteTable 仅支持 sqlite 方言,当前: ${String(runtime.dbDialect)}`);
985
808
  }
986
-
987
809
  const quotedSourceTable = quoteIdentifier("sqlite", tableName);
988
- const info = await runtime.db.unsafe<SqliteTableInfoRow[]>(`PRAGMA table_info(${quotedSourceTable})`);
810
+ const info = await runtime.db.unsafe(`PRAGMA table_info(${quotedSourceTable})`);
989
811
  const existingCols = info.data.map((r) => r.name);
990
812
  const businessCols = Object.keys(fields).map((k) => snakeCase(k));
991
813
  const targetCols = ["id", "created_at", "updated_at", "deleted_at", "state", ...businessCols];
992
814
  const tmpTable = `${tableName}__tmp__${Date.now()}`;
993
-
994
815
  await createTable(runtime, tmpTable, fields);
995
-
996
816
  const commonCols = targetCols.filter((c) => existingCols.includes(c));
997
817
  if (commonCols.length > 0) {
998
818
  const colsSql = commonCols.map((c) => quoteIdentifier("sqlite", c)).join(", ");
999
819
  const quotedTmpTable = quoteIdentifier("sqlite", tmpTable);
1000
820
  await runtime.db.unsafe(`INSERT INTO ${quotedTmpTable} (${colsSql}) SELECT ${colsSql} FROM ${quotedSourceTable}`);
1001
821
  }
1002
-
1003
822
  await runtime.db.unsafe(`DROP TABLE ${quotedSourceTable}`);
1004
823
  const quotedTmpTable = quoteIdentifier("sqlite", tmpTable);
1005
824
  await runtime.db.unsafe(`ALTER TABLE ${quotedTmpTable} RENAME TO ${quotedSourceTable}`);
1006
825
  }
1007
-
1008
826
  /**
1009
827
  * 将表结构计划应用到数据库
1010
828
  */
1011
- async function applyTablePlan(runtime: SyncRuntime, tableName: string, fields: Record<string, FieldDefinition>, plan: TablePlan): Promise<void> {
1012
- if (!plan || !plan.changed) return;
1013
-
829
+ async function applyTablePlan(runtime, tableName, fields, plan) {
830
+ if (!plan || !plan.changed)
831
+ return;
1014
832
  // A) 结构变更(ADD/MODIFY):SQLite 走重建表;其余方言走 ALTER TABLE
1015
833
  if (runtime.dbDialect === "sqlite") {
1016
834
  if (plan.modifyClauses.length > 0 || plan.defaultClauses.length > 0) {
1017
835
  await rebuildSqliteTable(runtime, tableName, fields);
1018
- } else {
836
+ }
837
+ else {
1019
838
  for (const c of plan.addClauses) {
1020
839
  const stmt = `ALTER TABLE ${quoteIdentifier(runtime.dbDialect, tableName)} ${c}`;
1021
840
  await runtime.db.unsafe(stmt);
1022
841
  }
1023
842
  }
1024
- } else {
843
+ }
844
+ else {
1025
845
  const clauses = [...plan.addClauses, ...plan.modifyClauses];
1026
846
  if (clauses.length > 0) {
1027
847
  const tableQuoted = quoteIdentifier(runtime.dbDialect, tableName);
1028
848
  const stmt = runtime.dbDialect === "mysql" ? `ALTER TABLE ${tableQuoted} ALGORITHM=INSTANT, LOCK=NONE, ${clauses.join(", ")}` : `ALTER TABLE ${tableQuoted} ${clauses.join(", ")}`;
1029
- if (runtime.dbDialect === "mysql") await executeDDLSafely(runtime.db, stmt);
1030
- else await runtime.db.unsafe(stmt);
849
+ if (runtime.dbDialect === "mysql")
850
+ await executeDDLSafely(runtime.db, stmt);
851
+ else
852
+ await runtime.db.unsafe(stmt);
1031
853
  }
1032
854
  }
1033
-
1034
855
  // B) 默认值变更:SQLite 不支持在线修改默认值(需要重建表),其余方言按子句执行
1035
856
  if (plan.defaultClauses.length > 0) {
1036
857
  if (runtime.dbDialect === "sqlite") {
1037
858
  Logger.warn(`SQLite 不支持修改默认值,表 ${tableName} 的默认值变更已跳过`);
1038
- } else {
859
+ }
860
+ else {
1039
861
  const tableQuoted = quoteIdentifier(runtime.dbDialect, tableName);
1040
862
  const stmt = runtime.dbDialect === "mysql" ? `ALTER TABLE ${tableQuoted} ALGORITHM=INSTANT, LOCK=NONE, ${plan.defaultClauses.join(", ")}` : `ALTER TABLE ${tableQuoted} ${plan.defaultClauses.join(", ")}`;
1041
- if (runtime.dbDialect === "mysql") await executeDDLSafely(runtime.db, stmt);
1042
- else await runtime.db.unsafe(stmt);
863
+ if (runtime.dbDialect === "mysql")
864
+ await executeDDLSafely(runtime.db, stmt);
865
+ else
866
+ await runtime.db.unsafe(stmt);
1043
867
  }
1044
868
  }
1045
-
1046
869
  // C) 索引动作:不同方言的 DDL 策略由 buildIndexSQL 统一生成
1047
870
  for (const act of plan.indexActions) {
1048
871
  const stmt = buildIndexSQL(runtime.dbDialect, tableName, act.indexName, act.fieldName, act.action);
@@ -1050,15 +873,16 @@ async function applyTablePlan(runtime: SyncRuntime, tableName: string, fields: R
1050
873
  await runtime.db.unsafe(stmt);
1051
874
  if (act.action === "create") {
1052
875
  Logger.debug(`[索引变化] 新建索引 ${tableName}.${act.indexName} (${act.fieldName})`);
1053
- } else {
876
+ }
877
+ else {
1054
878
  Logger.debug(`[索引变化] 删除索引 ${tableName}.${act.indexName} (${act.fieldName})`);
1055
879
  }
1056
- } catch (error: any) {
880
+ }
881
+ catch (error) {
1057
882
  Logger.error({ err: error, table: tableName, index: act.indexName, field: act.fieldName }, `${act.action === "create" ? "创建" : "删除"}索引失败`);
1058
883
  throw error;
1059
884
  }
1060
885
  }
1061
-
1062
886
  // D) PG 列注释:独立 SQL 执行(COMMENT ON COLUMN)
1063
887
  if (runtime.dbDialect === "postgresql" && plan.commentActions && plan.commentActions.length > 0) {
1064
888
  for (const stmt of plan.commentActions) {
@@ -1066,20 +890,16 @@ async function applyTablePlan(runtime: SyncRuntime, tableName: string, fields: R
1066
890
  }
1067
891
  }
1068
892
  }
1069
-
1070
893
  /**
1071
894
  * 创建表(包含系统字段和业务字段)
1072
895
  */
1073
- async function createTable(runtime: SyncRuntime, tableName: string, fields: Record<string, FieldDefinition>, systemIndexFields: ReadonlyArray<string> = SYSTEM_INDEX_FIELDS): Promise<void> {
896
+ async function createTable(runtime, tableName, fields, systemIndexFields = SYSTEM_INDEX_FIELDS) {
1074
897
  const colDefs = [...buildSystemColumnDefs(runtime.dbDialect), ...buildBusinessColumnDefs(runtime.dbDialect, fields)];
1075
-
1076
898
  const cols = colDefs.join(",\n ");
1077
899
  const tableQuoted = quoteIdentifier(runtime.dbDialect, tableName);
1078
900
  const { ENGINE, CHARSET, COLLATE } = MYSQL_TABLE_CONFIG;
1079
901
  const createSQL = runtime.dbDialect === "mysql" ? `CREATE TABLE ${tableQuoted} (\n ${cols}\n ) ENGINE=${ENGINE} DEFAULT CHARSET=${CHARSET} COLLATE=${COLLATE}` : `CREATE TABLE ${tableQuoted} (\n ${cols}\n )`;
1080
-
1081
902
  await runtime.db.unsafe(createSQL);
1082
-
1083
903
  if (runtime.dbDialect === "postgresql") {
1084
904
  for (const f of SYSTEM_FIELDS) {
1085
905
  const escaped = String(f.comment).replace(/'/g, "''");
@@ -1087,11 +907,9 @@ async function createTable(runtime: SyncRuntime, tableName: string, fields: Reco
1087
907
  const stmt = `COMMENT ON COLUMN ${tableQuoted}.${colQuoted} IS '${escaped}'`;
1088
908
  await runtime.db.unsafe(stmt);
1089
909
  }
1090
-
1091
910
  for (const [fieldKey, fieldDef] of Object.entries(fields)) {
1092
911
  const dbFieldName = snakeCase(fieldKey);
1093
912
  const colQuoted = quoteIdentifier(runtime.dbDialect, dbFieldName);
1094
-
1095
913
  const fieldName = fieldDef.name && fieldDef.name !== "null" ? String(fieldDef.name) : "";
1096
914
  const escaped = fieldName.replace(/'/g, "''");
1097
915
  const valueSql = fieldName ? `'${escaped}'` : "NULL";
@@ -1099,14 +917,11 @@ async function createTable(runtime: SyncRuntime, tableName: string, fields: Reco
1099
917
  await runtime.db.unsafe(stmt);
1100
918
  }
1101
919
  }
1102
-
1103
- const indexTasks: Array<Promise<unknown>> = [];
1104
-
1105
- let existingIndexes: Record<string, string[]> = {};
920
+ const indexTasks = [];
921
+ let existingIndexes = {};
1106
922
  if (runtime.dbDialect === "mysql") {
1107
923
  existingIndexes = await getTableIndexesRuntime(runtime, tableName);
1108
924
  }
1109
-
1110
925
  for (const sysField of systemIndexFields) {
1111
926
  const indexName = `idx_${sysField}`;
1112
927
  if (runtime.dbDialect === "mysql" && existingIndexes[indexName]) {
@@ -1115,10 +930,8 @@ async function createTable(runtime: SyncRuntime, tableName: string, fields: Reco
1115
930
  const stmt = buildIndexSQL(runtime.dbDialect, tableName, indexName, sysField, "create");
1116
931
  indexTasks.push(runtime.db.unsafe(stmt));
1117
932
  }
1118
-
1119
933
  for (const [fieldKey, fieldDef] of Object.entries(fields)) {
1120
934
  const dbFieldName = snakeCase(fieldKey);
1121
-
1122
935
  if (fieldDef.index === true && fieldDef.unique !== true) {
1123
936
  const indexName = `idx_${dbFieldName}`;
1124
937
  if (runtime.dbDialect === "mysql" && existingIndexes[indexName]) {
@@ -1128,75 +941,63 @@ async function createTable(runtime: SyncRuntime, tableName: string, fields: Reco
1128
941
  indexTasks.push(runtime.db.unsafe(stmt));
1129
942
  }
1130
943
  }
1131
-
1132
944
  if (indexTasks.length > 0) {
1133
945
  await Promise.all(indexTasks);
1134
946
  }
1135
947
  }
1136
-
1137
- async function modifyTableRuntime(runtime: SyncRuntime, tableName: string, fields: Record<string, FieldDefinition>): Promise<TablePlan> {
948
+ async function modifyTableRuntime(runtime, tableName, fields) {
1138
949
  // 1) 读取现有元信息(列/索引)
1139
950
  const existingColumns = await getTableColumnsRuntime(runtime, tableName);
1140
951
  const existingIndexes = await getTableIndexesRuntime(runtime, tableName);
1141
-
1142
952
  // 2) 规划变更(先 plan,后统一 apply)
1143
953
  let changed = false;
1144
-
1145
- const addClauses: string[] = [];
1146
- const modifyClauses: string[] = [];
1147
- const defaultClauses: string[] = [];
1148
- const indexActions: Array<{ action: "create" | "drop"; indexName: string; fieldName: string }> = [];
1149
-
954
+ const addClauses = [];
955
+ const modifyClauses = [];
956
+ const defaultClauses = [];
957
+ const indexActions = [];
1150
958
  // 3) 对比业务字段:新增/变更(类型/长度/可空/默认值/注释)
1151
959
  for (const [fieldKey, fieldDef] of Object.entries(fields)) {
1152
960
  const dbFieldName = snakeCase(fieldKey);
1153
-
1154
961
  if (existingColumns[dbFieldName]) {
1155
962
  const comparison = compareFieldDefinition(runtime.dbDialect, existingColumns[dbFieldName], fieldDef);
1156
963
  if (comparison.length > 0) {
1157
964
  for (const c of comparison) {
1158
- const changeLabel = CHANGE_TYPE_LABELS[c.type as keyof typeof CHANGE_TYPE_LABELS] || "未知";
965
+ const changeLabel = CHANGE_TYPE_LABELS[c.type] || "未知";
1159
966
  Logger.debug(` ~ 修改 ${dbFieldName} ${changeLabel}: ${c.current} -> ${c.expected}`);
1160
967
  }
1161
-
1162
968
  if (isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && fieldDef.max !== null) {
1163
- if (existingColumns[dbFieldName].max! > fieldDef.max) {
969
+ if (existingColumns[dbFieldName].max > fieldDef.max) {
1164
970
  Logger.warn(`[跳过危险变更] ${tableName}.${dbFieldName} 长度收缩 ${existingColumns[dbFieldName].max} -> ${fieldDef.max} 已被跳过(需手动处理)`);
1165
971
  }
1166
972
  }
1167
-
1168
973
  const hasTypeChange = comparison.some((c) => c.type === "datatype");
1169
974
  const hasLengthChange = comparison.some((c) => c.type === "length");
1170
975
  const onlyDefaultChanged = comparison.every((c) => c.type === "default");
1171
976
  const defaultChanged = comparison.some((c) => c.type === "default");
1172
-
1173
977
  if (hasTypeChange) {
1174
978
  const typeChange = comparison.find((c) => c.type === "datatype");
1175
979
  const currentType = String(typeChange?.current || "").toLowerCase();
1176
980
  const typeMapping = getTypeMapping(runtime.dbDialect);
1177
981
  const expectedType = typeMapping[fieldDef.type]?.toLowerCase() || "";
1178
-
1179
982
  if (!isCompatibleTypeChange(currentType, expectedType)) {
1180
983
  const errorMsg = [`禁止字段类型变更: ${tableName}.${dbFieldName}`, `当前类型: ${typeChange?.current}`, `目标类型: ${typeChange?.expected}`, "说明: 仅允许宽化型变更(如 INT->BIGINT, VARCHAR->TEXT),其他类型变更需要手动处理"].join("\n");
1181
984
  throw new Error(errorMsg);
1182
985
  }
1183
986
  Logger.debug(`[兼容类型变更] ${tableName}.${dbFieldName} ${currentType} -> ${expectedType}`);
1184
987
  }
1185
-
1186
988
  if (defaultChanged) {
1187
989
  const actualDefault = resolveDefaultValue(fieldDef.default, fieldDef.type);
1188
-
1189
- let v: string | null = null;
990
+ let v = null;
1190
991
  if (actualDefault !== "null") {
1191
992
  const defaultSql = generateDefaultSql(actualDefault, fieldDef.type);
1192
993
  v = defaultSql.trim().replace(/^DEFAULT\s+/, "");
1193
994
  }
1194
-
1195
995
  if (v !== null && v !== "") {
1196
996
  if (runtime.dbDialect === "postgresql") {
1197
997
  const colQuoted = quoteIdentifier(runtime.dbDialect, dbFieldName);
1198
998
  defaultClauses.push(`ALTER COLUMN ${colQuoted} SET DEFAULT ${v}`);
1199
- } else if (runtime.dbDialect === "mysql" && onlyDefaultChanged) {
999
+ }
1000
+ else if (runtime.dbDialect === "mysql" && onlyDefaultChanged) {
1200
1001
  if (fieldDef.type !== "text") {
1201
1002
  const colQuoted = quoteIdentifier(runtime.dbDialect, dbFieldName);
1202
1003
  defaultClauses.push(`ALTER COLUMN ${colQuoted} SET DEFAULT ${v}`);
@@ -1204,26 +1005,26 @@ async function modifyTableRuntime(runtime: SyncRuntime, tableName: string, field
1204
1005
  }
1205
1006
  }
1206
1007
  }
1207
-
1208
1008
  if (!onlyDefaultChanged) {
1209
1009
  let skipModify = false;
1210
1010
  if (hasLengthChange && isStringOrArrayType(fieldDef.type) && existingColumns[dbFieldName].max && fieldDef.max !== null) {
1211
- const isShrink = existingColumns[dbFieldName].max! > fieldDef.max;
1212
- if (isShrink) skipModify = true;
1011
+ const isShrink = existingColumns[dbFieldName].max > fieldDef.max;
1012
+ if (isShrink)
1013
+ skipModify = true;
1213
1014
  }
1214
-
1215
- if (!skipModify) modifyClauses.push(generateDDLClause(runtime.dbDialect, fieldKey, fieldDef, false));
1015
+ if (!skipModify)
1016
+ modifyClauses.push(generateDDLClause(runtime.dbDialect, fieldKey, fieldDef, false));
1216
1017
  }
1217
1018
  changed = true;
1218
1019
  }
1219
- } else {
1020
+ }
1021
+ else {
1220
1022
  addClauses.push(generateDDLClause(runtime.dbDialect, fieldKey, fieldDef, true));
1221
1023
  changed = true;
1222
1024
  }
1223
1025
  }
1224
-
1225
1026
  // 4) 补齐系统字段(除 id 外)
1226
- const systemFieldNames = SYSTEM_FIELDS.filter((f) => f.name !== "id").map((f) => f.name) as string[];
1027
+ const systemFieldNames = SYSTEM_FIELDS.filter((f) => f.name !== "id").map((f) => f.name);
1227
1028
  for (const sysFieldName of systemFieldNames) {
1228
1029
  if (!existingColumns[sysFieldName]) {
1229
1030
  const colDef = getSystemColumnDef(runtime.dbDialect, sysFieldName);
@@ -1234,7 +1035,6 @@ async function modifyTableRuntime(runtime: SyncRuntime, tableName: string, field
1234
1035
  }
1235
1036
  }
1236
1037
  }
1237
-
1238
1038
  // 5) 索引动作:系统字段索引 + 业务字段单列索引
1239
1039
  for (const sysField of SYSTEM_INDEX_FIELDS) {
1240
1040
  const idxName = `idx_${sysField}`;
@@ -1244,28 +1044,25 @@ async function modifyTableRuntime(runtime: SyncRuntime, tableName: string, field
1244
1044
  changed = true;
1245
1045
  }
1246
1046
  }
1247
-
1248
1047
  for (const [fieldKey, fieldDef] of Object.entries(fields)) {
1249
1048
  const dbFieldName = snakeCase(fieldKey);
1250
-
1251
1049
  const indexName = `idx_${dbFieldName}`;
1252
1050
  if (fieldDef.index && !fieldDef.unique && !existingIndexes[indexName]) {
1253
1051
  indexActions.push({ action: "create", indexName: indexName, fieldName: dbFieldName });
1254
1052
  changed = true;
1255
- } else if (!fieldDef.index && existingIndexes[indexName] && existingIndexes[indexName].length === 1) {
1053
+ }
1054
+ else if (!fieldDef.index && existingIndexes[indexName] && existingIndexes[indexName].length === 1) {
1256
1055
  indexActions.push({ action: "drop", indexName: indexName, fieldName: dbFieldName });
1257
1056
  changed = true;
1258
1057
  }
1259
1058
  }
1260
-
1261
1059
  // 6) PG 注释动作(MySQL 在列定义里带 COMMENT;SQLite 无列注释)
1262
- const commentActions: string[] = [];
1060
+ const commentActions = [];
1263
1061
  if (runtime.dbDialect === "postgresql") {
1264
1062
  const tableQuoted = quoteIdentifier(runtime.dbDialect, tableName);
1265
1063
  for (const [fieldKey, fieldDef] of Object.entries(fields)) {
1266
1064
  const dbFieldName = snakeCase(fieldKey);
1267
1065
  const colQuoted = quoteIdentifier(runtime.dbDialect, dbFieldName);
1268
-
1269
1066
  if (existingColumns[dbFieldName]) {
1270
1067
  const curr = existingColumns[dbFieldName].comment || "";
1271
1068
  const want = fieldDef.name && fieldDef.name !== "null" ? String(fieldDef.name) : "";
@@ -1277,11 +1074,9 @@ async function modifyTableRuntime(runtime: SyncRuntime, tableName: string, field
1277
1074
  }
1278
1075
  }
1279
1076
  }
1280
-
1281
1077
  // 7) 汇总计划并应用
1282
1078
  changed = addClauses.length > 0 || modifyClauses.length > 0 || defaultClauses.length > 0 || indexActions.length > 0 || commentActions.length > 0;
1283
-
1284
- const plan: TablePlan = {
1079
+ const plan = {
1285
1080
  changed: changed,
1286
1081
  addClauses: addClauses,
1287
1082
  modifyClauses: modifyClauses,
@@ -1289,10 +1084,8 @@ async function modifyTableRuntime(runtime: SyncRuntime, tableName: string, field
1289
1084
  indexActions: indexActions,
1290
1085
  commentActions: commentActions
1291
1086
  };
1292
-
1293
1087
  if (plan.changed) {
1294
1088
  await applyTablePlan(runtime, tableName, fields, plan);
1295
1089
  }
1296
-
1297
1090
  return plan;
1298
1091
  }