befly 3.10.18 → 3.11.1

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 (223) 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} +11 -36
  4. package/dist/befly.js +15621 -0
  5. package/dist/befly.min.js +21 -0
  6. package/dist/checks/checkApi.d.ts +1 -0
  7. package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
  8. package/dist/checks/checkHook.d.ts +1 -0
  9. package/dist/checks/checkHook.js +86 -0
  10. package/dist/checks/checkMenu.d.ts +7 -0
  11. package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
  12. package/dist/checks/checkPlugin.d.ts +1 -0
  13. package/dist/checks/checkPlugin.js +86 -0
  14. package/dist/checks/checkTable.d.ts +6 -0
  15. package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
  16. package/dist/configs/presetFields.d.ts +4 -0
  17. package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
  18. package/dist/configs/presetRegexp.d.ts +145 -0
  19. package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
  20. package/dist/hooks/auth.d.ts +7 -0
  21. package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
  22. package/dist/hooks/cors.d.ts +11 -0
  23. package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
  24. package/dist/hooks/parser.d.ts +14 -0
  25. package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
  26. package/dist/hooks/permission.d.ts +14 -0
  27. package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
  28. package/dist/hooks/validator.d.ts +11 -0
  29. package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
  30. package/dist/index.d.ts +26 -0
  31. package/{main.ts → dist/index.js} +61 -100
  32. package/dist/lib/asyncContext.d.ts +21 -0
  33. package/dist/lib/asyncContext.js +27 -0
  34. package/dist/lib/cacheHelper.d.ts +95 -0
  35. package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
  36. package/dist/lib/cacheKeys.d.ts +23 -0
  37. package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
  38. package/dist/lib/cipher.d.ts +153 -0
  39. package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
  40. package/dist/lib/connect.d.ts +91 -0
  41. package/{lib/connect.ts → dist/lib/connect.js} +47 -88
  42. package/dist/lib/dbDialect.d.ts +87 -0
  43. package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
  44. package/dist/lib/dbHelper.d.ts +204 -0
  45. package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
  46. package/dist/lib/dbUtils.d.ts +68 -0
  47. package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
  48. package/dist/lib/jwt.d.ts +13 -0
  49. package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
  50. package/dist/lib/logger.d.ts +42 -0
  51. package/dist/lib/logger.js +1144 -0
  52. package/dist/lib/redisHelper.d.ts +185 -0
  53. package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
  54. package/dist/lib/sqlBuilder.d.ts +160 -0
  55. package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
  56. package/dist/lib/sqlCheck.d.ts +23 -0
  57. package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
  58. package/dist/lib/validator.d.ts +45 -0
  59. package/{lib/validator.ts → dist/lib/validator.js} +44 -61
  60. package/dist/loader/loadApis.d.ts +12 -0
  61. package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
  62. package/dist/loader/loadHooks.d.ts +7 -0
  63. package/dist/loader/loadHooks.js +35 -0
  64. package/dist/loader/loadPlugins.d.ts +8 -0
  65. package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
  66. package/dist/paths.d.ts +93 -0
  67. package/{paths.ts → dist/paths.js} +6 -19
  68. package/dist/plugins/cache.d.ts +16 -0
  69. package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
  70. package/dist/plugins/cipher.d.ts +12 -0
  71. package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
  72. package/dist/plugins/config.d.ts +12 -0
  73. package/dist/plugins/config.js +8 -0
  74. package/dist/plugins/db.d.ts +16 -0
  75. package/{plugins/db.ts → dist/plugins/db.js} +11 -17
  76. package/dist/plugins/jwt.d.ts +12 -0
  77. package/dist/plugins/jwt.js +12 -0
  78. package/dist/plugins/logger.d.ts +32 -0
  79. package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
  80. package/dist/plugins/redis.d.ts +16 -0
  81. package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
  82. package/dist/plugins/tool.d.ts +81 -0
  83. package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
  84. package/dist/router/api.d.ts +14 -0
  85. package/dist/router/api.js +107 -0
  86. package/dist/router/static.d.ts +9 -0
  87. package/{router/static.ts → dist/router/static.js} +20 -34
  88. package/dist/scripts/ensureDist.d.ts +1 -0
  89. package/dist/scripts/ensureDist.js +296 -0
  90. package/dist/sync/syncApi.d.ts +3 -0
  91. package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
  92. package/dist/sync/syncCache.d.ts +2 -0
  93. package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
  94. package/dist/sync/syncDev.d.ts +6 -0
  95. package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
  96. package/dist/sync/syncMenu.d.ts +14 -0
  97. package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
  98. package/dist/sync/syncTable.d.ts +151 -0
  99. package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
  100. package/{types → dist/types}/api.d.ts +12 -51
  101. package/dist/types/api.js +4 -0
  102. package/{types → dist/types}/befly.d.ts +32 -227
  103. package/dist/types/befly.js +4 -0
  104. package/{types → dist/types}/cache.d.ts +7 -15
  105. package/dist/types/cache.js +4 -0
  106. package/dist/types/cipher.d.ts +27 -0
  107. package/dist/types/cipher.js +7 -0
  108. package/{types → dist/types}/common.d.ts +8 -33
  109. package/dist/types/common.js +5 -0
  110. package/{types → dist/types}/context.d.ts +3 -5
  111. package/dist/types/context.js +4 -0
  112. package/{types → dist/types}/crypto.d.ts +0 -3
  113. package/dist/types/crypto.js +4 -0
  114. package/dist/types/database.d.ts +138 -0
  115. package/dist/types/database.js +4 -0
  116. package/dist/types/hook.d.ts +17 -0
  117. package/dist/types/hook.js +6 -0
  118. package/dist/types/jwt.d.ts +75 -0
  119. package/dist/types/jwt.js +4 -0
  120. package/dist/types/logger.d.ts +59 -0
  121. package/dist/types/logger.js +6 -0
  122. package/dist/types/plugin.d.ts +16 -0
  123. package/dist/types/plugin.js +6 -0
  124. package/dist/types/redis.d.ts +71 -0
  125. package/dist/types/redis.js +4 -0
  126. package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
  127. package/dist/types/roleApisCache.js +8 -0
  128. package/dist/types/sync.d.ts +92 -0
  129. package/dist/types/sync.js +4 -0
  130. package/dist/types/table.d.ts +34 -0
  131. package/dist/types/table.js +4 -0
  132. package/dist/types/validate.d.ts +67 -0
  133. package/dist/types/validate.js +4 -0
  134. package/dist/utils/calcPerfTime.d.ts +4 -0
  135. package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
  136. package/dist/utils/convertBigIntFields.d.ts +11 -0
  137. package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
  138. package/dist/utils/cors.d.ts +8 -0
  139. package/{utils/cors.ts → dist/utils/cors.js} +1 -3
  140. package/dist/utils/disableMenusGlob.d.ts +13 -0
  141. package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
  142. package/dist/utils/fieldClear.d.ts +11 -0
  143. package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
  144. package/dist/utils/getClientIp.d.ts +6 -0
  145. package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
  146. package/dist/utils/importDefault.d.ts +1 -0
  147. package/dist/utils/importDefault.js +29 -0
  148. package/dist/utils/isDirentDirectory.d.ts +2 -0
  149. package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
  150. package/dist/utils/loadMenuConfigs.d.ts +29 -0
  151. package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
  152. package/dist/utils/mergeAndConcat.d.ts +7 -0
  153. package/dist/utils/mergeAndConcat.js +72 -0
  154. package/dist/utils/processAtSymbol.d.ts +4 -0
  155. package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
  156. package/dist/utils/processInfo.d.ts +24 -0
  157. package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
  158. package/dist/utils/response.d.ts +20 -0
  159. package/{utils/response.ts → dist/utils/response.js} +28 -49
  160. package/dist/utils/scanAddons.d.ts +17 -0
  161. package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
  162. package/dist/utils/scanConfig.d.ts +26 -0
  163. package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
  164. package/dist/utils/scanCoreBuiltins.d.ts +3 -0
  165. package/dist/utils/scanCoreBuiltins.js +65 -0
  166. package/dist/utils/scanFiles.d.ts +30 -0
  167. package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
  168. package/dist/utils/scanSources.d.ts +10 -0
  169. package/dist/utils/scanSources.js +46 -0
  170. package/dist/utils/sortModules.d.ts +28 -0
  171. package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
  172. package/dist/utils/util.d.ts +84 -0
  173. package/dist/utils/util.js +262 -0
  174. package/package.json +26 -34
  175. package/.gitignore +0 -0
  176. package/bunfig.toml +0 -3
  177. package/checks/checkHook.ts +0 -48
  178. package/checks/checkPlugin.ts +0 -48
  179. package/configs/presetRegexp.ts +0 -225
  180. package/docs/README.md +0 -98
  181. package/docs/api/api.md +0 -1921
  182. package/docs/guide/examples.md +0 -926
  183. package/docs/guide/quickstart.md +0 -354
  184. package/docs/hooks/auth.md +0 -38
  185. package/docs/hooks/cors.md +0 -28
  186. package/docs/hooks/hook.md +0 -838
  187. package/docs/hooks/parser.md +0 -19
  188. package/docs/hooks/rateLimit.md +0 -47
  189. package/docs/infra/redis.md +0 -628
  190. package/docs/plugins/cipher.md +0 -61
  191. package/docs/plugins/database.md +0 -189
  192. package/docs/plugins/plugin.md +0 -986
  193. package/docs/reference/addon.md +0 -510
  194. package/docs/reference/config.md +0 -573
  195. package/docs/reference/logger.md +0 -495
  196. package/docs/reference/sync.md +0 -478
  197. package/docs/reference/table.md +0 -763
  198. package/docs/reference/validator.md +0 -620
  199. package/lib/asyncContext.ts +0 -43
  200. package/lib/logger.ts +0 -811
  201. package/loader/loadHooks.ts +0 -51
  202. package/plugins/config.ts +0 -13
  203. package/plugins/jwt.ts +0 -15
  204. package/router/api.ts +0 -130
  205. package/tsconfig.json +0 -8
  206. package/types/database.d.ts +0 -541
  207. package/types/hook.d.ts +0 -25
  208. package/types/jwt.d.ts +0 -118
  209. package/types/logger.d.ts +0 -65
  210. package/types/plugin.d.ts +0 -19
  211. package/types/redis.d.ts +0 -83
  212. package/types/sync.d.ts +0 -398
  213. package/types/table.d.ts +0 -216
  214. package/types/validate.d.ts +0 -69
  215. package/utils/arrayKeysToCamel.ts +0 -18
  216. package/utils/configTypes.ts +0 -3
  217. package/utils/genShortId.ts +0 -12
  218. package/utils/importDefault.ts +0 -21
  219. package/utils/keysToCamel.ts +0 -22
  220. package/utils/keysToSnake.ts +0 -22
  221. package/utils/pickFields.ts +0 -19
  222. package/utils/scanSources.ts +0 -64
  223. package/utils/sqlLog.ts +0 -37
@@ -1,90 +1,110 @@
1
- import type { MenuConfig } from "../types/sync.ts";
2
- import type { AddonInfo } from "./scanAddons.ts";
3
- import type { ViewDirMeta } from "befly-shared/utils/scanViewsDir";
4
-
5
1
  import { existsSync } from "node:fs";
6
2
  import { readdir, readFile } from "node:fs/promises";
7
-
8
- import { cleanDirName, extractDefinePageMetaFromScriptSetup, extractScriptSetupBlock } from "befly-shared/utils/scanViewsDir";
9
3
  import { join } from "pathe";
10
-
11
- import { Logger } from "../lib/logger.ts";
12
- import { isDirentDirectory } from "./isDirentDirectory.ts";
13
-
14
- export async function scanViewsDirToMenuConfigs(viewsDir: string, prefix: string, parentPath: string = ""): Promise<MenuConfig[]> {
4
+ import { Logger } from "../lib/logger";
5
+ import { isDirentDirectory } from "./isDirentDirectory";
6
+ /**
7
+ * 清理目录名中的数字后缀
8
+ * 如:login_1 login, index_2 index
9
+ */
10
+ export function cleanDirName(name) {
11
+ return String(name).replace(/_\d+$/, "");
12
+ }
13
+ /**
14
+ * 只取第一个 <script ... setup ...> 块
15
+ */
16
+ export function extractScriptSetupBlock(vueContent) {
17
+ const openTag = /<script\b[^>]*\bsetup\b[^>]*>/i.exec(vueContent);
18
+ if (!openTag) {
19
+ return null;
20
+ }
21
+ const start = openTag.index + openTag[0].length;
22
+ const closeIndex = vueContent.indexOf("</script>", start);
23
+ if (closeIndex < 0) {
24
+ return null;
25
+ }
26
+ return vueContent.slice(start, closeIndex);
27
+ }
28
+ /**
29
+ * 从 <script setup> 中提取 definePage({ meta })
30
+ *
31
+ * 简化约束:
32
+ * - 每个页面只有一个 definePage
33
+ * - title 是纯字符串字面量
34
+ * - order 是数字字面量(可选)
35
+ * - 不考虑变量/表达式/多段 meta 组合
36
+ */
37
+ export function extractDefinePageMetaFromScriptSetup(scriptSetup) {
38
+ const titleMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?title\s*:\s*(["'`])([^"'`]+)\1/);
39
+ if (!titleMatch) {
40
+ return null;
41
+ }
42
+ const orderMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?order\s*:\s*(\d+)/);
43
+ return {
44
+ title: titleMatch[2],
45
+ order: orderMatch ? Number(orderMatch[1]) : undefined
46
+ };
47
+ }
48
+ export async function scanViewsDirToMenuConfigs(viewsDir, prefix, parentPath = "") {
15
49
  if (!existsSync(viewsDir)) {
16
50
  return [];
17
51
  }
18
-
19
- const menus: MenuConfig[] = [];
52
+ const menus = [];
20
53
  const entries = await readdir(viewsDir, { withFileTypes: true });
21
-
22
54
  for (const entry of entries) {
23
55
  if (!isDirentDirectory(viewsDir, entry) || entry.name === "components") {
24
56
  continue;
25
57
  }
26
-
27
58
  const dirPath = join(viewsDir, entry.name);
28
59
  const indexVuePath = join(dirPath, "index.vue");
29
-
30
60
  if (!existsSync(indexVuePath)) {
31
61
  continue;
32
62
  }
33
-
34
- let meta: ViewDirMeta | null = null;
63
+ let meta = null;
35
64
  try {
36
65
  const content = await readFile(indexVuePath, "utf-8");
37
-
38
66
  const scriptSetup = extractScriptSetupBlock(content);
39
67
  if (!scriptSetup) {
40
68
  Logger.warn({ path: indexVuePath }, "index.vue 缺少 <script setup>,已跳过该目录菜单同步");
41
69
  continue;
42
70
  }
43
-
44
71
  meta = extractDefinePageMetaFromScriptSetup(scriptSetup);
45
72
  if (!meta?.title) {
46
73
  Logger.warn({ path: indexVuePath }, "index.vue 未声明 definePage({ meta: { title, order? } }),已跳过该目录菜单同步");
47
74
  continue;
48
75
  }
49
- } catch (error: any) {
76
+ }
77
+ catch (error) {
50
78
  Logger.warn({ err: error, path: indexVuePath }, "读取 index.vue 失败");
51
79
  continue;
52
80
  }
53
-
54
81
  if (!meta?.title) {
55
82
  continue;
56
83
  }
57
-
58
84
  const cleanName = cleanDirName(entry.name);
59
- let menuPath: string;
85
+ let menuPath;
60
86
  if (cleanName === "index") {
61
87
  menuPath = parentPath;
62
- } else {
88
+ }
89
+ else {
63
90
  menuPath = parentPath ? `${parentPath}/${cleanName}` : `/${cleanName}`;
64
91
  }
65
-
66
92
  const fullPath = prefix ? (menuPath ? `${prefix}${menuPath}` : prefix) : menuPath || "/";
67
-
68
- const menu: MenuConfig = {
93
+ const menu = {
69
94
  name: meta.title,
70
95
  path: fullPath,
71
96
  sort: meta.order ?? 999999
72
97
  };
73
-
74
98
  const children = await scanViewsDirToMenuConfigs(dirPath, prefix, menuPath);
75
99
  if (children.length > 0) {
76
100
  menu.children = children;
77
101
  }
78
-
79
102
  menus.push(menu);
80
103
  }
81
-
82
104
  menus.sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999));
83
-
84
105
  return menus;
85
106
  }
86
-
87
- export function getParentPath(path: string): string {
107
+ export function getParentPath(path) {
88
108
  // "/a/b" => "/a"
89
109
  // "/a" => ""
90
110
  const parts = path.split("/").filter((p) => !!p);
@@ -93,10 +113,8 @@ export function getParentPath(path: string): string {
93
113
  }
94
114
  return `/${parts.slice(0, -1).join("/")}`;
95
115
  }
96
-
97
- export async function loadMenuConfigs(addons: AddonInfo[]): Promise<MenuConfig[]> {
98
- const allMenus: MenuConfig[] = [];
99
-
116
+ export async function loadMenuConfigs(addons) {
117
+ const allMenus = [];
100
118
  for (const addon of addons) {
101
119
  const adminViewsDirByTopLevel = join(addon.fullPath, "adminViews");
102
120
  const adminViewsDirByViews = join(addon.fullPath, "views", "admin");
@@ -104,7 +122,6 @@ export async function loadMenuConfigs(addons: AddonInfo[]): Promise<MenuConfig[]
104
122
  if (!adminViewsDir) {
105
123
  continue;
106
124
  }
107
-
108
125
  try {
109
126
  const prefix = `/${addon.source}/${addon.name}`;
110
127
  const menus = await scanViewsDirToMenuConfigs(adminViewsDir, prefix);
@@ -113,19 +130,16 @@ export async function loadMenuConfigs(addons: AddonInfo[]): Promise<MenuConfig[]
113
130
  allMenus.push(menu);
114
131
  }
115
132
  }
116
- } catch (error: any) {
117
- Logger.warn(
118
- {
119
- err: error,
120
- addon: addon.name,
121
- addonSource: addon.source,
122
- dir: adminViewsDir
123
- },
124
- "扫描 addon views 目录失败"
125
- );
133
+ }
134
+ catch (error) {
135
+ Logger.warn({
136
+ err: error,
137
+ addon: addon.name,
138
+ addonSource: addon.source,
139
+ dir: adminViewsDir
140
+ }, "扫描 addon views 目录失败");
126
141
  }
127
142
  }
128
-
129
143
  const menusJsonPath = join(process.cwd(), "menus.json");
130
144
  if (existsSync(menusJsonPath)) {
131
145
  try {
@@ -136,10 +150,10 @@ export async function loadMenuConfigs(addons: AddonInfo[]): Promise<MenuConfig[]
136
150
  allMenus.push(menu);
137
151
  }
138
152
  }
139
- } catch (error: any) {
153
+ }
154
+ catch (error) {
140
155
  Logger.warn({ err: error }, "读取项目 menus.json 失败");
141
156
  }
142
157
  }
143
-
144
158
  return allMenus;
145
159
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 深度合并对象,并对数组执行 concat(保持 scanConfig 现有语义)。
3
+ * - undefined 会被忽略
4
+ * - plain object 深合并
5
+ * - array 与 array 合并为新数组(保持输入不被污染)
6
+ */
7
+ export declare function mergeAndConcat(...items: any[]): any;
@@ -0,0 +1,72 @@
1
+ import { isPlainObject } from "./util";
2
+ function cloneDeepLoose(value) {
3
+ if (Array.isArray(value)) {
4
+ const arr = [];
5
+ for (const item of value) {
6
+ arr.push(cloneDeepLoose(item));
7
+ }
8
+ return arr;
9
+ }
10
+ if (isPlainObject(value)) {
11
+ const out = {};
12
+ for (const key of Object.keys(value)) {
13
+ out[key] = cloneDeepLoose(value[key]);
14
+ }
15
+ return out;
16
+ }
17
+ return value;
18
+ }
19
+ function mergeInto(target, source) {
20
+ if (source === undefined) {
21
+ return target;
22
+ }
23
+ if (Array.isArray(target) && Array.isArray(source)) {
24
+ for (const item of source) {
25
+ target.push(cloneDeepLoose(item));
26
+ }
27
+ return target;
28
+ }
29
+ if (isPlainObject(target) && isPlainObject(source)) {
30
+ for (const key of Object.keys(source)) {
31
+ const srcVal = source[key];
32
+ if (srcVal === undefined) {
33
+ continue;
34
+ }
35
+ const curVal = target[key];
36
+ if (Array.isArray(curVal) && Array.isArray(srcVal)) {
37
+ const nextArr = [];
38
+ for (const item of curVal) {
39
+ nextArr.push(cloneDeepLoose(item));
40
+ }
41
+ for (const item of srcVal) {
42
+ nextArr.push(cloneDeepLoose(item));
43
+ }
44
+ target[key] = nextArr;
45
+ continue;
46
+ }
47
+ if (isPlainObject(curVal) && isPlainObject(srcVal)) {
48
+ target[key] = mergeInto(cloneDeepLoose(curVal), srcVal);
49
+ continue;
50
+ }
51
+ target[key] = cloneDeepLoose(srcVal);
52
+ }
53
+ return target;
54
+ }
55
+ return cloneDeepLoose(source);
56
+ }
57
+ /**
58
+ * 深度合并对象,并对数组执行 concat(保持 scanConfig 现有语义)。
59
+ * - undefined 会被忽略
60
+ * - plain object 深合并
61
+ * - array 与 array 合并为新数组(保持输入不被污染)
62
+ */
63
+ export function mergeAndConcat(...items) {
64
+ let acc = {};
65
+ for (const item of items) {
66
+ if (item === undefined) {
67
+ continue;
68
+ }
69
+ acc = mergeInto(acc, item);
70
+ }
71
+ return acc;
72
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 处理字段定义:将 @ 符号引用替换为实际字段定义
3
+ */
4
+ export declare function processAtSymbol(fields: Record<string, any>, apiName: string, routePath: string): Record<string, any>;
@@ -1,25 +1,21 @@
1
- import { presetFields } from "../configs/presetFields.ts";
2
-
1
+ import { presetFields } from "../configs/presetFields";
3
2
  /**
4
3
  * 处理字段定义:将 @ 符号引用替换为实际字段定义
5
4
  */
6
- export function processFields(fields: Record<string, any>, apiName: string, routePath: string): Record<string, any> {
7
- if (!fields || typeof fields !== "object") return fields;
8
-
9
- const processed: Record<string, any> = {};
5
+ export function processAtSymbol(fields, apiName, routePath) {
6
+ if (!fields || typeof fields !== "object")
7
+ return fields;
8
+ const processed = {};
10
9
  for (const [key, value] of Object.entries(fields)) {
11
10
  if (typeof value === "string" && value.startsWith("@")) {
12
11
  if (presetFields[value]) {
13
12
  processed[key] = presetFields[value];
14
13
  continue;
15
14
  }
16
-
17
15
  const validKeys = Object.keys(presetFields).join(", ");
18
16
  throw new Error(`API [${apiName}] (${routePath}) 字段 [${key}] 引用了未定义的预设字段 "${value}"。可用的预设字段有: ${validKeys}`);
19
17
  }
20
-
21
18
  processed[key] = value;
22
19
  }
23
-
24
20
  return processed;
25
21
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 进程角色信息
3
+ */
4
+ export interface ProcessRole {
5
+ /** 进程角色:primary(主进程)或 worker(工作进程) */
6
+ role: "primary" | "worker";
7
+ /** 实例 ID(PM2 或 Bun Worker) */
8
+ instanceId: string | null;
9
+ /** 运行环境:bun-cluster、pm2-cluster 或 standalone */
10
+ env: "bun-cluster" | "pm2-cluster" | "standalone";
11
+ }
12
+ /**
13
+ * 获取当前进程角色信息
14
+ * @returns 进程角色、实例 ID 和运行环境
15
+ */
16
+ export declare function getProcessRole(): ProcessRole;
17
+ /**
18
+ * 检测当前进程是否为主进程
19
+ * 用于集群模式下避免重复执行同步任务
20
+ * - Bun 集群:BUN_WORKER_ID 为空时是主进程
21
+ * - PM2 集群:PM2_INSTANCE_ID 为 '0' 或不存在时是主进程
22
+ * @returns 是否为主进程
23
+ */
24
+ export declare function isPrimaryProcess(): boolean;
@@ -1,23 +1,10 @@
1
- /**
2
- * 进程角色信息
3
- */
4
- export interface ProcessRole {
5
- /** 进程角色:primary(主进程)或 worker(工作进程) */
6
- role: "primary" | "worker";
7
- /** 实例 ID(PM2 或 Bun Worker) */
8
- instanceId: string | null;
9
- /** 运行环境:bun-cluster、pm2-cluster 或 standalone */
10
- env: "bun-cluster" | "pm2-cluster" | "standalone";
11
- }
12
-
13
1
  /**
14
2
  * 获取当前进程角色信息
15
3
  * @returns 进程角色、实例 ID 和运行环境
16
4
  */
17
- export function getProcessRole(): ProcessRole {
5
+ export function getProcessRole() {
18
6
  const bunWorkerId = process.env.BUN_WORKER_ID;
19
7
  const pm2InstanceId = process.env.PM2_INSTANCE_ID;
20
-
21
8
  // Bun 集群模式
22
9
  if (bunWorkerId !== undefined) {
23
10
  return {
@@ -26,7 +13,6 @@ export function getProcessRole(): ProcessRole {
26
13
  env: "bun-cluster"
27
14
  };
28
15
  }
29
-
30
16
  // PM2 集群模式
31
17
  if (pm2InstanceId !== undefined) {
32
18
  return {
@@ -35,7 +21,6 @@ export function getProcessRole(): ProcessRole {
35
21
  env: "pm2-cluster"
36
22
  };
37
23
  }
38
-
39
24
  // 单进程模式
40
25
  return {
41
26
  role: "primary",
@@ -43,7 +28,6 @@ export function getProcessRole(): ProcessRole {
43
28
  env: "standalone"
44
29
  };
45
30
  }
46
-
47
31
  /**
48
32
  * 检测当前进程是否为主进程
49
33
  * 用于集群模式下避免重复执行同步任务
@@ -51,6 +35,6 @@ export function getProcessRole(): ProcessRole {
51
35
  * - PM2 集群:PM2_INSTANCE_ID 为 '0' 或不存在时是主进程
52
36
  * @returns 是否为主进程
53
37
  */
54
- export function isPrimaryProcess(): boolean {
38
+ export function isPrimaryProcess() {
55
39
  return getProcessRole().role === "primary";
56
40
  }
@@ -0,0 +1,20 @@
1
+ import type { RequestContext } from "../types/context";
2
+ /**
3
+ * 创建错误响应(专用于 Hook 中间件)
4
+ * 在钩子中提前拦截请求时使用
5
+ * @param ctx - 请求上下文
6
+ * @param msg - 错误消息
7
+ * @param code - 错误码,默认 1
8
+ * @param data - 附加数据,默认 null
9
+ * @param detail - 详细信息,用于标记具体提示位置,默认 null
10
+ * @param reasonCode - 拦截原因标识(用于统计/聚合),默认 null
11
+ * @returns Response 对象
12
+ */
13
+ export declare function ErrorResponse(ctx: RequestContext, msg: string, code?: number, data?: any, detail?: any, reasonCode?: string | null): Response;
14
+ /**
15
+ * 创建最终响应(专用于 API 路由结尾)
16
+ * 自动处理 ctx.response/ctx.result,并记录请求日志
17
+ * @param ctx - 请求上下文
18
+ * @returns Response 对象
19
+ */
20
+ export declare function FinalResponse(ctx: RequestContext): Response;
@@ -1,7 +1,4 @@
1
- import type { RequestContext } from "../types/context.ts";
2
-
3
- import { Logger } from "../lib/logger.ts";
4
-
1
+ import { Logger } from "../lib/logger";
5
2
  /**
6
3
  * 创建错误响应(专用于 Hook 中间件)
7
4
  * 在钩子中提前拦截请求时使用
@@ -13,62 +10,48 @@ import { Logger } from "../lib/logger.ts";
13
10
  * @param reasonCode - 拦截原因标识(用于统计/聚合),默认 null
14
11
  * @returns Response 对象
15
12
  */
16
- export function ErrorResponse(ctx: RequestContext, msg: string, code: number = 1, data: any = null, detail: any = null, reasonCode: string | null = null): Response {
13
+ export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, reasonCode = null) {
17
14
  // 记录拦截日志
18
15
  if (ctx.requestId) {
19
16
  // requestId/route/user/duration 等字段由 ALS 统一注入,避免在 msg 中重复拼接
20
- Logger.info(
21
- {
22
- event: "request_blocked",
23
- reason: msg,
24
- reasonCode: reasonCode,
25
- code: code,
26
- detail: detail
27
- },
28
- "request blocked"
29
- );
30
- }
31
-
32
- return Response.json(
33
- {
17
+ Logger.info({
18
+ event: "request_blocked",
19
+ reason: msg,
20
+ reasonCode: reasonCode,
34
21
  code: code,
35
- msg: msg,
36
- data: data,
37
22
  detail: detail
38
- },
39
- {
40
- headers: ctx.corsHeaders
41
- }
42
- );
23
+ }, "request blocked");
24
+ }
25
+ return Response.json({
26
+ code: code,
27
+ msg: msg,
28
+ data: data,
29
+ detail: detail
30
+ }, {
31
+ headers: ctx.corsHeaders
32
+ });
43
33
  }
44
-
45
34
  /**
46
35
  * 创建最终响应(专用于 API 路由结尾)
47
36
  * 自动处理 ctx.response/ctx.result,并记录请求日志
48
37
  * @param ctx - 请求上下文
49
38
  * @returns Response 对象
50
39
  */
51
- export function FinalResponse(ctx: RequestContext): Response {
40
+ export function FinalResponse(ctx) {
52
41
  // 记录请求日志
53
42
  if (ctx.api && ctx.requestId) {
54
43
  // requestId/route/user/duration 等字段由 ALS 统一注入,避免在 msg 中重复拼接
55
- Logger.info(
56
- {
57
- event: "request_done"
58
- },
59
- "request done"
60
- );
44
+ Logger.info({
45
+ event: "request_done"
46
+ }, "request done");
61
47
  }
62
-
63
48
  // 1. 如果已经有 response,直接返回
64
49
  if (ctx.response) {
65
50
  return ctx.response;
66
51
  }
67
-
68
52
  // 2. 如果有 result,格式化为响应
69
53
  if (ctx.result !== undefined) {
70
54
  let result = ctx.result;
71
-
72
55
  // 如果是字符串,自动包裹为成功响应
73
56
  if (typeof result === "string") {
74
57
  result = {
@@ -85,7 +68,6 @@ export function FinalResponse(ctx: RequestContext): Response {
85
68
  };
86
69
  }
87
70
  }
88
-
89
71
  // 处理 BigInt 序列化问题
90
72
  if (result && typeof result === "object") {
91
73
  const jsonString = JSON.stringify(result, (key, value) => (typeof value === "bigint" ? value.toString() : value));
@@ -95,21 +77,18 @@ export function FinalResponse(ctx: RequestContext): Response {
95
77
  "Content-Type": "application/json"
96
78
  }
97
79
  });
98
- } else {
80
+ }
81
+ else {
99
82
  return Response.json(result, {
100
83
  headers: ctx.corsHeaders
101
84
  });
102
85
  }
103
86
  }
104
-
105
87
  // 3. 默认响应:没有生成响应
106
- return Response.json(
107
- {
108
- code: 1,
109
- msg: "未生成响应"
110
- },
111
- {
112
- headers: ctx.corsHeaders
113
- }
114
- );
88
+ return Response.json({
89
+ code: 1,
90
+ msg: "未生成响应"
91
+ }, {
92
+ headers: ctx.corsHeaders
93
+ });
115
94
  }
@@ -0,0 +1,17 @@
1
+ export type AddonSource = "addon" | "app";
2
+ export interface AddonInfo {
3
+ /** addon 来源 */
4
+ source: AddonSource;
5
+ /** addon 来源中文名 */
6
+ sourceName: string;
7
+ /** addon 名称(目录名,通常是 demo/admin 等) */
8
+ name: string;
9
+ /** camelCase(name) */
10
+ camelName: string;
11
+ /** addon 根目录绝对路径 */
12
+ rootDir: string;
13
+ /** 兼容字段:历史上使用 fullPath 表示 rootDir */
14
+ fullPath: string;
15
+ }
16
+ /** 扫描 node_modules/@befly-addon + 项目 addons/(项目优先级更高) */
17
+ export declare const scanAddons: () => AddonInfo[];
@@ -1,55 +1,25 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
-
3
- import { camelCase } from "es-toolkit/string";
4
2
  import { join, resolve } from "pathe";
5
-
6
- import { appAddonsDir, appDir } from "../paths.ts";
7
- import { isDirentDirectory } from "./isDirentDirectory.ts";
8
-
9
- export type AddonSource = "addon" | "app";
10
-
11
- export interface AddonInfo {
12
- /** addon 来源 */
13
- source: AddonSource;
14
-
15
- /** addon 来源中文名 */
16
- sourceName: string;
17
-
18
- /** addon 名称(目录名,通常是 demo/admin 等) */
19
- name: string;
20
-
21
- /** camelCase(name) */
22
- camelName: string;
23
-
24
- /** addon 根目录绝对路径 */
25
- rootDir: string;
26
-
27
- /** 兼容字段:历史上使用 fullPath 表示 rootDir */
28
- fullPath: string;
29
- }
30
-
3
+ import { appAddonsDir, appDir } from "../paths";
4
+ import { isDirentDirectory } from "./isDirentDirectory";
5
+ import { camelCase } from "./util";
31
6
  /** 扫描 node_modules/@befly-addon + 项目 addons/(项目优先级更高) */
32
- export const scanAddons = (): AddonInfo[] => {
33
- const addonMap = new Map<string, AddonInfo>();
34
-
35
- const scanBaseDir = (baseDir: string, source: AddonSource, sourceName: string) => {
7
+ export const scanAddons = () => {
8
+ const addonMap = new Map();
9
+ const scanBaseDir = (baseDir, source, sourceName) => {
36
10
  if (!existsSync(baseDir)) {
37
11
  return;
38
12
  }
39
-
40
13
  const entries = readdirSync(baseDir, { withFileTypes: true });
41
14
  for (const entry of entries) {
42
15
  if (entry.name.startsWith("_")) {
43
16
  continue;
44
17
  }
45
-
46
18
  if (!isDirentDirectory(baseDir, entry)) {
47
19
  continue;
48
20
  }
49
-
50
21
  const rootDir = resolve(baseDir, entry.name);
51
-
52
- const info: AddonInfo = {
22
+ const info = {
53
23
  source: source,
54
24
  sourceName: sourceName,
55
25
  name: entry.name,
@@ -57,16 +27,12 @@ export const scanAddons = (): AddonInfo[] => {
57
27
  rootDir: rootDir,
58
28
  fullPath: rootDir
59
29
  };
60
-
61
30
  addonMap.set(entry.name, info);
62
31
  }
63
32
  };
64
-
65
33
  // node_modules 中的 @befly-addon
66
34
  scanBaseDir(join(appDir, "node_modules", "@befly-addon"), "addon", "组件");
67
-
68
35
  // 项目本地 addons(同名覆盖 node_modules)
69
36
  scanBaseDir(appAddonsDir, "app", "项目");
70
-
71
37
  return Array.from(addonMap.values());
72
38
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 加载配置选项
3
+ * - 该类型为 scanConfig 专用:按“就近放置”原则直接与实现放在同文件,避免拆到单独的 *Types.ts 造成目录噪音。
4
+ */
5
+ export interface LoadConfigOptions {
6
+ /** 当前工作目录,默认 process.cwd() */
7
+ cwd?: string;
8
+ /** 目录数组:要搜索的目录路径(相对于 cwd) */
9
+ dirs: string[];
10
+ /** 文件数组:要匹配的文件名 */
11
+ files: string[];
12
+ /** 文件扩展名,默认 ['.js', '.ts', '.json'] */
13
+ extensions?: string[];
14
+ /** 加载模式:'first' = 返回第一个找到的配置(默认),'merge' = 合并所有配置 */
15
+ mode?: "merge" | "first";
16
+ /** 指定要提取的字段路径数组,如 ['menus', 'database.host'],为空则返回完整对象 */
17
+ paths?: string[];
18
+ /** 默认配置,会与找到的配置合并(优先级最低) */
19
+ defaults?: Record<string, any>;
20
+ }
21
+ /**
22
+ * 扫描并合并配置文件(矩阵搜索:dirs × files)
23
+ * @param options - 加载选项
24
+ * @returns 合并后的配置对象(或第一个找到的配置)
25
+ */
26
+ export declare function scanConfig(options: LoadConfigOptions): Promise<Record<string, any>>;