befly 3.10.17 → 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 (218) 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} +8 -31
  4. package/dist/checks/checkApi.d.ts +1 -0
  5. package/{checks/checkApi.ts → dist/checks/checkApi.js} +10 -27
  6. package/dist/checks/checkHook.d.ts +1 -0
  7. package/{checks/checkHook.ts → dist/checks/checkHook.js} +10 -19
  8. package/dist/checks/checkMenu.d.ts +7 -0
  9. package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +15 -50
  10. package/dist/checks/checkPlugin.d.ts +1 -0
  11. package/{checks/checkPlugin.ts → dist/checks/checkPlugin.js} +10 -19
  12. package/dist/checks/checkTable.d.ts +6 -0
  13. package/{checks/checkTable.ts → dist/checks/checkTable.js} +16 -40
  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} +5 -9
  20. package/dist/hooks/cors.d.ts +9 -0
  21. package/{hooks/cors.ts → dist/hooks/cors.js} +2 -12
  22. package/dist/hooks/parser.d.ts +12 -0
  23. package/{hooks/parser.ts → dist/hooks/parser.js} +27 -42
  24. package/dist/hooks/permission.d.ts +12 -0
  25. package/{hooks/permission.ts → dist/hooks/permission.js} +11 -22
  26. package/dist/hooks/validator.d.ts +9 -0
  27. package/{hooks/validator.ts → dist/hooks/validator.js} +5 -12
  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} +43 -103
  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} +46 -87
  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} +73 -230
  42. package/dist/lib/dbUtils.d.ts +68 -0
  43. package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +49 -123
  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} +201 -278
  48. package/dist/lib/redisHelper.d.ts +185 -0
  49. package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +95 -139
  50. package/dist/lib/sqlBuilder.d.ts +160 -0
  51. package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +131 -277
  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} +43 -60
  56. package/dist/loader/loadApis.d.ts +12 -0
  57. package/{loader/loadApis.ts → dist/loader/loadApis.js} +7 -17
  58. package/dist/loader/loadHooks.d.ts +8 -0
  59. package/{loader/loadHooks.ts → dist/loader/loadHooks.js} +5 -19
  60. package/dist/loader/loadPlugins.d.ts +8 -0
  61. package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +8 -20
  62. package/dist/main.d.ts +26 -0
  63. package/{main.ts → dist/main.js} +39 -78
  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} +4 -11
  68. package/dist/plugins/cipher.d.ts +10 -0
  69. package/{plugins/cipher.ts → dist/plugins/cipher.js} +1 -5
  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} +5 -13
  74. package/dist/plugins/jwt.d.ts +10 -0
  75. package/{plugins/jwt.ts → dist/plugins/jwt.js} +2 -7
  76. package/dist/plugins/logger.d.ts +28 -0
  77. package/{plugins/logger.ts → dist/plugins/logger.js} +2 -7
  78. package/dist/plugins/redis.d.ts +14 -0
  79. package/{plugins/redis.ts → dist/plugins/redis.js} +4 -9
  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} +17 -31
  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} +33 -53
  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} +27 -60
  94. package/dist/sync/syncMenu.d.ts +14 -0
  95. package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +61 -121
  96. package/dist/sync/syncTable.d.ts +151 -0
  97. package/{sync/syncTable.ts → dist/sync/syncTable.js} +168 -375
  98. package/{types → dist/types}/api.d.ts +12 -51
  99. package/dist/types/api.js +4 -0
  100. package/{types → dist/types}/befly.d.ts +32 -223
  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 +3 -5
  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} +4 -4
  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} +22 -49
  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} +4 -8
  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} +27 -48
  170. package/dist/utils/scanAddons.d.ts +17 -0
  171. package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +4 -38
  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} +25 -65
  176. package/dist/utils/scanSources.d.ts +10 -0
  177. package/{utils/scanSources.ts → dist/utils/scanSources.js} +16 -39
  178. package/dist/utils/sortModules.d.ts +28 -0
  179. package/{utils/sortModules.ts → dist/utils/sortModules.js} +24 -64
  180. package/dist/utils/sqlLog.d.ts +14 -0
  181. package/{utils/sqlLog.ts → dist/utils/sqlLog.js} +2 -14
  182. package/package.json +15 -32
  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/router/api.ts +0 -130
  207. package/tsconfig.json +0 -54
  208. package/types/database.d.ts +0 -541
  209. package/types/hook.d.ts +0 -25
  210. package/types/jwt.d.ts +0 -118
  211. package/types/logger.d.ts +0 -65
  212. package/types/plugin.d.ts +0 -19
  213. package/types/redis.d.ts +0 -83
  214. package/types/sync.d.ts +0 -398
  215. package/types/table.d.ts +0 -216
  216. package/types/validate.d.ts +0 -69
  217. package/utils/configTypes.ts +0 -3
  218. package/utils/importDefault.ts +0 -21
@@ -1,62 +1,42 @@
1
- import type { BeflyContext } from "../types/befly.js";
2
- import type { MenuConfig } from "../types/sync.js";
3
-
4
1
  import { Logger } from "../lib/logger.js";
5
2
  import { compileDisableMenuGlobRules, isMenuPathDisabledByGlobRules } from "../utils/disableMenusGlob.js";
6
3
  import { getParentPath } from "../utils/loadMenuConfigs.js";
7
-
8
- type MenuDef = {
9
- path: string;
10
- name: string;
11
- sort: number;
12
- parentPath: string;
13
- };
14
-
15
- function flattenMenusToDefMap(mergedMenus: MenuConfig[]): Map<string, MenuDef> {
4
+ function flattenMenusToDefMap(mergedMenus) {
16
5
  // 读取配置菜单:扁平化为 path => { name, sort, parentPath }
17
6
  // - 以 path 为唯一键:后出现的覆盖先出现的(与旧逻辑“同 path 多次同步同一条记录”一致)
18
7
  // parentPath 规则:
19
8
  // 1) 若 menu 显式携带 parentPath(包括空字符串),以其为准
20
9
  // 2) 否则使用“树结构”推导的父级(由 children 嵌套关系决定;根级为 "")
21
10
  // 3) 保底:若无法推导(极端情况),回退到 getParentPath(path)
22
- const menuDefMap = new Map<string, MenuDef>();
23
-
24
- const stack: Array<{ menu: MenuConfig; parentPathFromTree: string }> = [];
11
+ const menuDefMap = new Map();
12
+ const stack = [];
25
13
  for (const m of mergedMenus) {
26
14
  stack.push({ menu: m, parentPathFromTree: "" });
27
15
  }
28
-
29
16
  while (stack.length > 0) {
30
17
  const item = stack.pop();
31
18
  const menu = item ? item.menu : null;
32
19
  if (!menu) {
33
20
  continue;
34
21
  }
35
-
36
- const path = typeof (menu as any).path === "string" ? (menu as any).path : "";
37
-
38
- const rawChildren = (menu as any).children;
22
+ const path = typeof menu.path === "string" ? menu.path : "";
23
+ const rawChildren = menu.children;
39
24
  if (rawChildren && Array.isArray(rawChildren) && rawChildren.length > 0) {
40
25
  const nextParentPathFromTree = typeof path === "string" ? path : "";
41
26
  for (const child of rawChildren) {
42
27
  stack.push({ menu: child, parentPathFromTree: nextParentPathFromTree });
43
28
  }
44
29
  }
45
-
46
30
  if (!path) {
47
31
  continue;
48
32
  }
49
-
50
- const name = typeof (menu as any).name === "string" ? (menu as any).name : "";
33
+ const name = typeof menu.name === "string" ? menu.name : "";
51
34
  if (!name) {
52
35
  continue;
53
36
  }
54
-
55
- const sort = typeof (menu as any).sort === "number" ? (menu as any).sort : 999999;
56
-
57
- const hasExplicitParentPath = typeof (menu as any).parentPath === "string";
58
- const parentPath = hasExplicitParentPath ? ((menu as any).parentPath as string) : typeof item?.parentPathFromTree === "string" ? item.parentPathFromTree : getParentPath(path);
59
-
37
+ const sort = typeof menu.sort === "number" ? menu.sort : 999999;
38
+ const hasExplicitParentPath = typeof menu.parentPath === "string";
39
+ const parentPath = hasExplicitParentPath ? menu.parentPath : typeof item?.parentPathFromTree === "string" ? item.parentPathFromTree : getParentPath(path);
60
40
  menuDefMap.set(path, {
61
41
  path: path,
62
42
  name: name,
@@ -64,86 +44,68 @@ function flattenMenusToDefMap(mergedMenus: MenuConfig[]): Map<string, MenuDef> {
64
44
  parentPath: parentPath
65
45
  });
66
46
  }
67
-
68
47
  return menuDefMap;
69
48
  }
70
-
71
- export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Promise<void> {
49
+ export async function syncMenu(ctx, mergedMenus) {
72
50
  if (!ctx.db) {
73
51
  throw new Error("syncMenu: ctx.db 未初始化(Db 插件未加载或注入失败)");
74
52
  }
75
-
76
53
  if (!ctx.cache) {
77
54
  throw new Error("syncMenu: ctx.cache 未初始化(cache 插件未加载或注入失败)");
78
55
  }
79
-
80
56
  if (!ctx.config) {
81
57
  throw new Error("syncMenu: ctx.config 未初始化(config 插件未加载或注入失败)");
82
58
  }
83
-
84
59
  if (!(await ctx.db.tableExists("addon_admin_menu")).data) {
85
60
  Logger.debug(`addon_admin_menu 表不存在`);
86
61
  return;
87
62
  }
88
-
89
63
  // 防御性过滤:保证禁用菜单不会进入 DB(即使上游遗漏了 checkMenu 的过滤)
90
64
  const disableRules = compileDisableMenuGlobRules(ctx.config?.disableMenus);
91
- const filteredMergedMenus: MenuConfig[] =
92
- disableRules.length === 0
93
- ? mergedMenus
94
- : (() => {
95
- const filterMenusByDisableRules = (menus: MenuConfig[]): MenuConfig[] => {
96
- const filtered: MenuConfig[] = [];
97
-
98
- for (const menu of menus) {
99
- const menuPath = typeof (menu as any)?.path === "string" ? String((menu as any).path).trim() : "";
100
- if (menuPath && isMenuPathDisabledByGlobRules(menuPath, disableRules)) {
101
- continue;
102
- }
103
-
104
- const children = Array.isArray((menu as any)?.children) ? ((menu as any).children as MenuConfig[]) : null;
105
- if (children && children.length > 0) {
106
- const nextChildren = filterMenusByDisableRules(children);
107
- if (nextChildren.length > 0) {
108
- (menu as any).children = nextChildren;
109
- } else {
110
- delete (menu as any).children;
111
- }
112
- }
113
-
114
- filtered.push(menu);
115
- }
116
-
117
- return filtered;
118
- };
119
-
120
- return filterMenusByDisableRules(mergedMenus);
121
- })();
122
-
65
+ const filteredMergedMenus = disableRules.length === 0
66
+ ? mergedMenus
67
+ : (() => {
68
+ const filterMenusByDisableRules = (menus) => {
69
+ const filtered = [];
70
+ for (const menu of menus) {
71
+ const menuPath = typeof menu?.path === "string" ? String(menu.path).trim() : "";
72
+ if (menuPath && isMenuPathDisabledByGlobRules(menuPath, disableRules)) {
73
+ continue;
74
+ }
75
+ const children = Array.isArray(menu?.children) ? menu.children : null;
76
+ if (children && children.length > 0) {
77
+ const nextChildren = filterMenusByDisableRules(children);
78
+ if (nextChildren.length > 0) {
79
+ menu.children = nextChildren;
80
+ }
81
+ else {
82
+ delete menu.children;
83
+ }
84
+ }
85
+ filtered.push(menu);
86
+ }
87
+ return filtered;
88
+ };
89
+ return filterMenusByDisableRules(mergedMenus);
90
+ })();
123
91
  const menuDefMap = flattenMenusToDefMap(filteredMergedMenus);
124
-
125
- const configPaths = new Set<string>();
92
+ const configPaths = new Set();
126
93
  for (const p of menuDefMap.keys()) {
127
94
  configPaths.add(p);
128
95
  }
129
-
130
96
  const tableName = "addon_admin_menu";
131
-
132
97
  // 2) 批量同步(事务内):按 path diff 执行批量 insert/update/delete
133
- await ctx.db.trans(async (trans: any) => {
98
+ await ctx.db.trans(async (trans) => {
134
99
  // 读取全部菜单(用于清理禁用菜单:不分 state)
135
100
  const allExistingMenusAllState = await trans.getAll({
136
101
  table: tableName,
137
102
  fields: ["id", "name", "path", "parentPath", "sort", "state"]
138
- } as any);
139
-
103
+ });
140
104
  const existingListAllState = allExistingMenusAllState.data.lists || [];
141
- const existingList = existingListAllState.filter((m: any) => typeof m?.state === "number" && m.state >= 0);
142
-
143
- const existingMenuMap = new Map<string, any>();
144
- const duplicateIdSet = new Set<number>();
145
- const duplicatePathInfoMap = new Map<string, { keptId: number; removedIds: number[] }>();
146
-
105
+ const existingList = existingListAllState.filter((m) => typeof m?.state === "number" && m.state >= 0);
106
+ const existingMenuMap = new Map();
107
+ const duplicateIdSet = new Set();
108
+ const duplicatePathInfoMap = new Map();
147
109
  for (const record of existingList) {
148
110
  if (typeof record?.path !== "string" || !record.path) {
149
111
  continue;
@@ -151,36 +113,31 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
151
113
  if (typeof record?.id !== "number") {
152
114
  continue;
153
115
  }
154
-
155
116
  const existing = existingMenuMap.get(record.path);
156
117
  if (!existing) {
157
118
  existingMenuMap.set(record.path, record);
158
119
  continue;
159
120
  }
160
-
161
121
  const existingId = typeof existing?.id === "number" ? existing.id : 0;
162
122
  const recordId = record.id;
163
-
164
123
  // 保留 id 最大的一条(genTimeID 越大通常越新),其余标记为重复并清理
165
124
  if (recordId > existingId) {
166
125
  existingMenuMap.set(record.path, record);
167
-
168
126
  if (existingId > 0) {
169
127
  duplicateIdSet.add(existingId);
170
128
  }
171
-
172
- const info = duplicatePathInfoMap.get(record.path) || { keptId: recordId, removedIds: [] as number[] };
129
+ const info = duplicatePathInfoMap.get(record.path) || { keptId: recordId, removedIds: [] };
173
130
  info.keptId = recordId;
174
131
  if (existingId > 0) {
175
132
  info.removedIds.push(existingId);
176
133
  }
177
134
  duplicatePathInfoMap.set(record.path, info);
178
- } else {
135
+ }
136
+ else {
179
137
  if (recordId > 0) {
180
138
  duplicateIdSet.add(recordId);
181
139
  }
182
-
183
- const info = duplicatePathInfoMap.get(record.path) || { keptId: existingId, removedIds: [] as number[] };
140
+ const info = duplicatePathInfoMap.get(record.path) || { keptId: existingId, removedIds: [] };
184
141
  info.keptId = existingId;
185
142
  if (recordId > 0) {
186
143
  info.removedIds.push(recordId);
@@ -188,9 +145,8 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
188
145
  duplicatePathInfoMap.set(record.path, info);
189
146
  }
190
147
  }
191
-
192
148
  if (duplicatePathInfoMap.size > 0) {
193
- const examples: Array<{ path: string; keptId: number; removedIds: number[] }> = [];
149
+ const examples = [];
194
150
  for (const entry of duplicatePathInfoMap.entries()) {
195
151
  const path = entry[0];
196
152
  const info = entry[1];
@@ -199,22 +155,16 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
199
155
  break;
200
156
  }
201
157
  }
202
-
203
- Logger.warn(
204
- {
205
- table: tableName,
206
- duplicatePaths: duplicatePathInfoMap.size,
207
- duplicateIds: duplicateIdSet.size,
208
- examples: examples
209
- },
210
- "addon_admin_menu 检测到重复 path 记录:已保留 id 最大的一条并删除其余记录"
211
- );
158
+ Logger.warn({
159
+ table: tableName,
160
+ duplicatePaths: duplicatePathInfoMap.size,
161
+ duplicateIds: duplicateIdSet.size,
162
+ examples: examples
163
+ }, "addon_admin_menu 检测到重复 path 记录:已保留 id 最大的一条并删除其余记录");
212
164
  }
213
-
214
165
  // 2) 一次性算出 insert/update(仅依赖 path diff,不使用 pid,不预生成 id)
215
- const updList: Array<{ id: number; data: Record<string, any> }> = [];
216
- const insList: Array<Record<string, any>> = [];
217
-
166
+ const updList = [];
167
+ const insList = [];
218
168
  for (const def of menuDefMap.values()) {
219
169
  const existing = existingMenuMap.get(def.path);
220
170
  if (existing) {
@@ -231,7 +181,8 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
231
181
  }
232
182
  });
233
183
  }
234
- } else {
184
+ }
185
+ else {
235
186
  insList.push({
236
187
  name: def.name,
237
188
  path: def.path,
@@ -240,18 +191,14 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
240
191
  });
241
192
  }
242
193
  }
243
-
244
194
  if (updList.length > 0) {
245
195
  await trans.updBatch(tableName, updList);
246
196
  }
247
-
248
197
  if (insList.length > 0) {
249
198
  await trans.insBatch(tableName, insList);
250
199
  }
251
-
252
200
  // 3) 删除差集(DB - 配置,仅 state>=0) + 删除重复 path 的多余记录 + 删除禁用菜单(不分 state)
253
- const delIdSet = new Set<number>();
254
-
201
+ const delIdSet = new Set();
255
202
  // 3.1) 清理禁用菜单:只要命中 disableMenus,就强制删除(避免 menu/list 之类接口还能查到)
256
203
  if (disableRules.length > 0) {
257
204
  for (const record of existingListAllState) {
@@ -259,7 +206,6 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
259
206
  if (!recordPath) {
260
207
  continue;
261
208
  }
262
-
263
209
  if (isMenuPathDisabledByGlobRules(recordPath, disableRules)) {
264
210
  if (typeof record?.id === "number" && record.id > 0) {
265
211
  delIdSet.add(record.id);
@@ -267,7 +213,6 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
267
213
  }
268
214
  }
269
215
  }
270
-
271
216
  for (const record of existingList) {
272
217
  if (typeof record?.path !== "string" || !record.path) {
273
218
  continue;
@@ -278,30 +223,25 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
278
223
  }
279
224
  }
280
225
  }
281
-
282
226
  for (const id of duplicateIdSet) {
283
227
  if (typeof id === "number" && id > 0) {
284
228
  delIdSet.add(id);
285
229
  }
286
230
  }
287
-
288
231
  const delIds = Array.from(delIdSet);
289
-
290
232
  if (delIds.length > 0) {
291
233
  await trans.delForceBatch(tableName, delIds);
292
234
  }
293
235
  });
294
-
295
236
  // 缓存同步职责已收敛到 syncCache(启动流程单点调用),此处只负责 DB 同步。
296
237
  }
297
-
298
238
  // 仅测试用(避免将内部扫描逻辑变成稳定 API)
299
239
  export const __test__ = {
300
- scanViewsDir: async (viewsDir: string, prefix: string, parentPath: string = "") => {
240
+ scanViewsDir: async (viewsDir, prefix, parentPath = "") => {
301
241
  const mod = await import("../utils/loadMenuConfigs.js");
302
242
  return await mod.scanViewsDirToMenuConfigs(viewsDir, prefix, parentPath);
303
243
  },
304
- flattenMenusToDefMap: (mergedMenus: MenuConfig[]) => {
244
+ flattenMenusToDefMap: (mergedMenus) => {
305
245
  return flattenMenusToDefMap(mergedMenus);
306
246
  }
307
247
  };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * syncTable 命令 - 同步数据库表结构(单文件版)
3
+ *
4
+ * 说明:
5
+ * - 历史上该能力拆分在 packages/core/sync/syncTable/* 多个模块中
6
+ * - 现在按项目要求,将所有实现合并到本文件(目录 packages/core/sync/syncTable/ 已删除)
7
+ */
8
+ import type { DbDialectName } from "../lib/dbDialect.ts";
9
+ import type { BeflyContext } from "../types/befly.ts";
10
+ import type { DbResult, SqlInfo } from "../types/database.ts";
11
+ import type { ColumnInfo, FieldChange, IndexInfo } from "../types/sync.ts";
12
+ import type { FieldDefinition } from "../types/validate.ts";
13
+ import type { ScanFileResult } from "../utils/scanFiles.ts";
14
+ type SqlExecutor = {
15
+ unsafe<T = any>(sqlStr: string, params?: unknown[]): Promise<DbResult<T, SqlInfo>>;
16
+ };
17
+ type DbDialect = DbDialectName;
18
+ /**
19
+ * 文件导航(推荐阅读顺序)
20
+ * 1) syncTable(ctx, items) 入口(本段下方)
21
+ * 2) 版本/常量/方言判断(DB_VERSION_REQUIREMENTS 等)
22
+ * 3) 通用 DDL 工具(quote/type/default/ddl/index SQL)
23
+ * 4) Runtime I/O(只读元信息:表/列/索引/版本)
24
+ * 5) plan/apply(写变更:建表/改表/SQLite 重建)
25
+ */
26
+ type SyncTableFn = ((ctx: BeflyContext, items: ScanFileResult[]) => Promise<void>) & {
27
+ TestKit: typeof SYNC_TABLE_TEST_KIT;
28
+ };
29
+ /**
30
+ * 数据库同步命令入口(函数模式)
31
+ */
32
+ export declare const syncTable: SyncTableFn;
33
+ declare const SYNC_TABLE_TEST_KIT: {
34
+ DB_VERSION_REQUIREMENTS: {
35
+ readonly MYSQL_MIN_MAJOR: 8;
36
+ readonly POSTGRES_MIN_MAJOR: 17;
37
+ readonly SQLITE_MIN_VERSION: "3.50.0";
38
+ readonly SQLITE_MIN_VERSION_NUM: 35000;
39
+ };
40
+ CHANGE_TYPE_LABELS: {
41
+ readonly length: "长度";
42
+ readonly datatype: "类型";
43
+ readonly comment: "注释";
44
+ readonly default: "默认值";
45
+ readonly nullable: "可空约束";
46
+ readonly unique: "唯一约束";
47
+ };
48
+ MYSQL_TABLE_CONFIG: {
49
+ readonly ENGINE: "InnoDB";
50
+ readonly CHARSET: "utf8mb4";
51
+ readonly COLLATE: "utf8mb4_0900_ai_ci";
52
+ };
53
+ SYSTEM_INDEX_FIELDS: readonly string[];
54
+ getTypeMapping: typeof getTypeMapping;
55
+ quoteIdentifier: typeof quoteIdentifier;
56
+ escapeComment: typeof escapeComment;
57
+ applyFieldDefaults: typeof applyFieldDefaults;
58
+ isStringOrArrayType: typeof isStringOrArrayType;
59
+ getSqlType: typeof getSqlType;
60
+ resolveDefaultValue: typeof resolveDefaultValue;
61
+ generateDefaultSql: typeof generateDefaultSql;
62
+ buildIndexSQL: typeof buildIndexSQL;
63
+ buildSystemColumnDefs: typeof buildSystemColumnDefs;
64
+ buildBusinessColumnDefs: typeof buildBusinessColumnDefs;
65
+ generateDDLClause: typeof generateDDLClause;
66
+ isCompatibleTypeChange: typeof isCompatibleTypeChange;
67
+ compareFieldDefinition: typeof compareFieldDefinition;
68
+ tableExistsRuntime: typeof tableExistsRuntime;
69
+ getTableColumnsRuntime: typeof getTableColumnsRuntime;
70
+ getTableIndexesRuntime: typeof getTableIndexesRuntime;
71
+ createRuntime: (dbDialect: DbDialectName, db: SqlExecutor, dbName?: string) => SyncRuntime;
72
+ };
73
+ /**
74
+ * 获取字段类型映射(根据当前数据库类型)
75
+ */
76
+ declare function getTypeMapping(dbDialect: DbDialect): Record<string, string>;
77
+ /**
78
+ * 根据数据库类型引用标识符
79
+ */
80
+ declare function quoteIdentifier(dbDialect: DbDialect, identifier: string): string;
81
+ /**
82
+ * 转义 SQL 注释中的双引号
83
+ */
84
+ declare function escapeComment(str: string): string;
85
+ /**
86
+ * 为字段定义应用默认值
87
+ */
88
+ declare function applyFieldDefaults(fieldDef: any): void;
89
+ /**
90
+ * 判断是否为字符串或数组类型(需要长度参数)
91
+ */
92
+ declare function isStringOrArrayType(fieldType: string): boolean;
93
+ /**
94
+ * 获取 SQL 数据类型
95
+ */
96
+ declare function getSqlType(dbDialect: DbDialect, fieldType: string, fieldMax: number | null, unsigned?: boolean): string;
97
+ /**
98
+ * 处理默认值:将 null 或 'null' 字符串转换为对应类型的默认值
99
+ */
100
+ declare function resolveDefaultValue(fieldDefault: any, fieldType: string): any;
101
+ /**
102
+ * 生成 SQL DEFAULT 子句
103
+ */
104
+ declare function generateDefaultSql(actualDefault: any, fieldType: string): string;
105
+ /**
106
+ * 构建索引操作 SQL(统一使用在线策略)
107
+ */
108
+ declare function buildIndexSQL(dbDialect: DbDialect, tableName: string, indexName: string, fieldName: string, action: "create" | "drop"): string;
109
+ /**
110
+ * 构建系统字段列定义
111
+ */
112
+ declare function buildSystemColumnDefs(dbDialect: DbDialect): string[];
113
+ /**
114
+ * 构建业务字段列定义
115
+ */
116
+ declare function buildBusinessColumnDefs(dbDialect: DbDialect, fields: Record<string, FieldDefinition>): string[];
117
+ /**
118
+ * 生成字段 DDL 子句(不含 ALTER TABLE 前缀)
119
+ */
120
+ declare function generateDDLClause(dbDialect: DbDialect, fieldKey: string, fieldDef: FieldDefinition, isAdd?: boolean): string;
121
+ /**
122
+ * 判断是否为兼容的类型变更(宽化型变更,无数据丢失风险)
123
+ */
124
+ declare function isCompatibleTypeChange(currentType: string, newType: string): boolean;
125
+ type SyncRuntime = {
126
+ /**
127
+ * 当前数据库方言(mysql/postgresql/sqlite),决定 SQL 片段与元信息查询方式。
128
+ * 约束:必须与 ctx.config.db.type 一致(经归一化)。
129
+ */
130
+ dbDialect: DbDialect;
131
+ /**
132
+ * SQL 执行器:必须复用 ctx.db。
133
+ * 约束:syncTable 内部禁止新建 DB 连接/事务;runtime 仅保存引用,不拥有生命周期。
134
+ */
135
+ db: SqlExecutor;
136
+ /**
137
+ * 数据库名:主要用于 MySQL information_schema 查询。
138
+ * 约束:PG/SQLite 可以传空字符串;不要在非 MySQL 方言依赖该值。
139
+ */
140
+ dbName: string;
141
+ };
142
+ declare function tableExistsRuntime(runtime: SyncRuntime, tableName: string): Promise<boolean>;
143
+ declare function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string): Promise<{
144
+ [key: string]: ColumnInfo;
145
+ }>;
146
+ declare function getTableIndexesRuntime(runtime: SyncRuntime, tableName: string): Promise<IndexInfo>;
147
+ /**
148
+ * 比较字段定义变化
149
+ */
150
+ declare function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo, fieldDef: FieldDefinition): FieldChange[];
151
+ export {};