befly 3.8.19 → 3.8.20

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 (49) hide show
  1. package/README.md +7 -6
  2. package/bunfig.toml +1 -1
  3. package/lib/database.ts +28 -25
  4. package/lib/dbHelper.ts +3 -3
  5. package/lib/jwt.ts +90 -99
  6. package/lib/logger.ts +44 -23
  7. package/lib/redisHelper.ts +19 -22
  8. package/loader/loadApis.ts +69 -114
  9. package/loader/loadHooks.ts +65 -0
  10. package/loader/loadPlugins.ts +50 -219
  11. package/main.ts +106 -133
  12. package/package.json +15 -7
  13. package/paths.ts +20 -0
  14. package/plugins/cache.ts +1 -3
  15. package/plugins/db.ts +8 -11
  16. package/plugins/logger.ts +5 -3
  17. package/plugins/redis.ts +10 -14
  18. package/router/api.ts +60 -106
  19. package/router/root.ts +15 -12
  20. package/router/static.ts +54 -58
  21. package/sync/syncAll.ts +58 -0
  22. package/sync/syncApi.ts +264 -0
  23. package/sync/syncDb/apply.ts +194 -0
  24. package/sync/syncDb/constants.ts +76 -0
  25. package/sync/syncDb/ddl.ts +194 -0
  26. package/sync/syncDb/helpers.ts +200 -0
  27. package/sync/syncDb/index.ts +164 -0
  28. package/sync/syncDb/schema.ts +201 -0
  29. package/sync/syncDb/sqlite.ts +50 -0
  30. package/sync/syncDb/table.ts +321 -0
  31. package/sync/syncDb/tableCreate.ts +146 -0
  32. package/sync/syncDb/version.ts +72 -0
  33. package/sync/syncDb.ts +19 -0
  34. package/sync/syncDev.ts +206 -0
  35. package/sync/syncMenu.ts +331 -0
  36. package/tsconfig.json +2 -4
  37. package/types/api.d.ts +6 -0
  38. package/types/befly.d.ts +152 -28
  39. package/types/context.d.ts +29 -3
  40. package/types/hook.d.ts +35 -0
  41. package/types/index.ts +14 -1
  42. package/types/plugin.d.ts +6 -7
  43. package/types/sync.d.ts +403 -0
  44. package/check.ts +0 -378
  45. package/env.ts +0 -106
  46. package/lib/middleware.ts +0 -275
  47. package/types/env.ts +0 -65
  48. package/types/util.d.ts +0 -45
  49. package/util.ts +0 -257
@@ -4,30 +4,27 @@
4
4
  */
5
5
 
6
6
  import { RedisClient } from 'bun';
7
- import { Env } from '../env.js';
8
7
  import { Logger } from '../lib/logger.js';
9
8
  import { Database } from './database.js';
10
9
 
11
- /**
12
- * Redis 键前缀
13
- */
14
- const prefix = Env.REDIS_KEY_PREFIX ? `${Env.REDIS_KEY_PREFIX}:` : '';
15
-
16
10
  /**
17
11
  * Redis 助手类
18
12
  */
19
13
  export class RedisHelper {
20
14
  private client: RedisClient;
15
+ private prefix: string;
21
16
 
22
17
  /**
23
18
  * 构造函数
19
+ * @param prefix - Key 前缀
24
20
  */
25
- constructor() {
21
+ constructor(prefix: string = '') {
26
22
  const client = Database.getRedis();
27
23
  if (!client) {
28
24
  throw new Error('Redis 客户端未初始化,请先调用 Database.connectRedis()');
29
25
  }
30
26
  this.client = client;
27
+ this.prefix = prefix ? `${prefix}:` : '';
31
28
  }
32
29
 
33
30
  /**
@@ -40,7 +37,7 @@ export class RedisHelper {
40
37
  async setObject<T = any>(key: string, obj: T, ttl: number | null = null): Promise<string | null> {
41
38
  try {
42
39
  const data = JSON.stringify(obj);
43
- const pkey = `${prefix}${key}`;
40
+ const pkey = `${this.prefix}${key}`;
44
41
 
45
42
  if (ttl) {
46
43
  return await this.client.setex(pkey, ttl, data);
@@ -59,7 +56,7 @@ export class RedisHelper {
59
56
  */
60
57
  async getObject<T = any>(key: string): Promise<T | null> {
61
58
  try {
62
- const pkey = `${prefix}${key}`;
59
+ const pkey = `${this.prefix}${key}`;
63
60
  const data = await this.client.get(pkey);
64
61
  return data ? JSON.parse(data) : null;
65
62
  } catch (error: any) {
@@ -74,7 +71,7 @@ export class RedisHelper {
74
71
  */
75
72
  async delObject(key: string): Promise<void> {
76
73
  try {
77
- const pkey = `${prefix}${key}`;
74
+ const pkey = `${this.prefix}${key}`;
78
75
  await this.client.del(pkey);
79
76
  } catch (error: any) {
80
77
  Logger.error('Redis delObject 错误', error);
@@ -90,7 +87,7 @@ export class RedisHelper {
90
87
  */
91
88
  async genTimeID(): Promise<number> {
92
89
  const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
93
- const key = `${prefix}time_id_counter:${timestamp}`;
90
+ const key = `${this.prefix}time_id_counter:${timestamp}`;
94
91
 
95
92
  const counter = await this.client.incr(key);
96
93
  await this.client.expire(key, 1);
@@ -118,7 +115,7 @@ export class RedisHelper {
118
115
  }
119
116
 
120
117
  const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
121
- const key = `${prefix}time_id_counter:${timestamp}`;
118
+ const key = `${this.prefix}time_id_counter:${timestamp}`;
122
119
 
123
120
  // 使用 INCRBY 一次性获取 N 个连续计数
124
121
  const startCounter = await this.client.incrby(key, count);
@@ -143,7 +140,7 @@ export class RedisHelper {
143
140
  */
144
141
  async setString(key: string, value: string, ttl: number | null = null): Promise<string | null> {
145
142
  try {
146
- const pkey = `${prefix}${key}`;
143
+ const pkey = `${this.prefix}${key}`;
147
144
  if (ttl) {
148
145
  return await this.client.setex(pkey, ttl, value);
149
146
  }
@@ -160,7 +157,7 @@ export class RedisHelper {
160
157
  */
161
158
  async getString(key: string): Promise<string | null> {
162
159
  try {
163
- const pkey = `${prefix}${key}`;
160
+ const pkey = `${this.prefix}${key}`;
164
161
  return await this.client.get(pkey);
165
162
  } catch (error: any) {
166
163
  Logger.error('Redis getString 错误', error);
@@ -174,7 +171,7 @@ export class RedisHelper {
174
171
  */
175
172
  async exists(key: string): Promise<number> {
176
173
  try {
177
- const pkey = `${prefix}${key}`;
174
+ const pkey = `${this.prefix}${key}`;
178
175
  return await this.client.exists(pkey);
179
176
  } catch (error: any) {
180
177
  Logger.error('Redis exists 错误', error);
@@ -189,7 +186,7 @@ export class RedisHelper {
189
186
  */
190
187
  async expire(key: string, seconds: number): Promise<number> {
191
188
  try {
192
- const pkey = `${prefix}${key}`;
189
+ const pkey = `${this.prefix}${key}`;
193
190
  return await this.client.expire(pkey, seconds);
194
191
  } catch (error: any) {
195
192
  Logger.error('Redis expire 错误', error);
@@ -203,7 +200,7 @@ export class RedisHelper {
203
200
  */
204
201
  async ttl(key: string): Promise<number> {
205
202
  try {
206
- const pkey = `${prefix}${key}`;
203
+ const pkey = `${this.prefix}${key}`;
207
204
  return await this.client.ttl(pkey);
208
205
  } catch (error: any) {
209
206
  Logger.error('Redis ttl 错误', error);
@@ -221,7 +218,7 @@ export class RedisHelper {
221
218
  try {
222
219
  if (members.length === 0) return 0;
223
220
 
224
- const pkey = `${prefix}${key}`;
221
+ const pkey = `${this.prefix}${key}`;
225
222
  return await this.client.sadd(pkey, ...members);
226
223
  } catch (error: any) {
227
224
  Logger.error('Redis sadd 错误', error);
@@ -237,7 +234,7 @@ export class RedisHelper {
237
234
  */
238
235
  async sismember(key: string, member: string): Promise<number> {
239
236
  try {
240
- const pkey = `${prefix}${key}`;
237
+ const pkey = `${this.prefix}${key}`;
241
238
  return await this.client.sismember(pkey, member);
242
239
  } catch (error: any) {
243
240
  Logger.error('Redis sismember 错误', error);
@@ -252,7 +249,7 @@ export class RedisHelper {
252
249
  */
253
250
  async scard(key: string): Promise<number> {
254
251
  try {
255
- const pkey = `${prefix}${key}`;
252
+ const pkey = `${this.prefix}${key}`;
256
253
  return await this.client.scard(pkey);
257
254
  } catch (error: any) {
258
255
  Logger.error('Redis scard 错误', error);
@@ -267,7 +264,7 @@ export class RedisHelper {
267
264
  */
268
265
  async smembers(key: string): Promise<string[]> {
269
266
  try {
270
- const pkey = `${prefix}${key}`;
267
+ const pkey = `${this.prefix}${key}`;
271
268
  return await this.client.smembers(pkey);
272
269
  } catch (error: any) {
273
270
  Logger.error('Redis smembers 错误', error);
@@ -282,7 +279,7 @@ export class RedisHelper {
282
279
  */
283
280
  async del(key: string): Promise<number> {
284
281
  try {
285
- const pkey = `${prefix}${key}`;
282
+ const pkey = `${this.prefix}${key}`;
286
283
  return await this.client.del(pkey);
287
284
  } catch (error: any) {
288
285
  Logger.error('Redis del 错误', error);
@@ -1,18 +1,22 @@
1
1
  /**
2
2
  * API 加载器
3
- * 负责扫描和加载所有 API 路由(组件、用户)
3
+ * 负责扫描和加载所有 API 路由(组件、项目)
4
4
  */
5
5
 
6
- import { relative, basename, join } from 'pathe';
6
+ // 内部依赖
7
7
  import { existsSync } from 'node:fs';
8
+
9
+ // 外部依赖
10
+ import { relative, basename, join } from 'pathe';
8
11
  import { isPlainObject } from 'es-toolkit/compat';
12
+ import { calcPerfTime, scanFiles, scanAddons, getAddonDir, addonDirExists } from 'befly-util';
13
+
14
+ // 相对导入
9
15
  import { Logger } from '../lib/logger.js';
10
- import { calcPerfTime } from '../util.js';
11
16
  import { projectApiDir } from '../paths.js';
12
- import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
13
- import type { ApiRoute } from '../types/api.js';
14
17
 
15
- const API_GLOB_PATTERN = '**/*.{ts,js}';
18
+ // 类型导入
19
+ import type { ApiRoute } from '../types/api.js';
16
20
 
17
21
  /**
18
22
  * API 默认字段定义
@@ -52,103 +56,6 @@ const DEFAULT_API_FIELDS = {
52
56
  }
53
57
  } as const;
54
58
 
55
- /**
56
- * 扫描用户 API 文件
57
- */
58
- async function scanUserApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
59
- const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
60
-
61
- if (!existsSync(projectApiDir)) {
62
- return apis;
63
- }
64
-
65
- const glob = new Bun.Glob(API_GLOB_PATTERN);
66
-
67
- for await (const file of glob.scan({
68
- cwd: projectApiDir,
69
- onlyFiles: true,
70
- absolute: true
71
- })) {
72
- if (file.endsWith('.d.ts')) {
73
- continue;
74
- }
75
- const apiPath = relative(projectApiDir, file).replace(/\.(ts|js)$/, '');
76
- if (apiPath.indexOf('_') !== -1) continue;
77
-
78
- apis.push({
79
- file: file,
80
- routePrefix: '',
81
- displayName: '用户'
82
- });
83
- }
84
-
85
- return apis;
86
- }
87
-
88
- /**
89
- * 扫描组件 API 文件
90
- */
91
- async function scanAddonApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
92
- const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
93
- const glob = new Bun.Glob(API_GLOB_PATTERN);
94
- const addons = scanAddons();
95
-
96
- for (const addon of addons) {
97
- if (!addonDirExists(addon, 'apis')) continue;
98
-
99
- const addonApiDir = getAddonDir(addon, 'apis');
100
- for await (const file of glob.scan({
101
- cwd: addonApiDir,
102
- onlyFiles: true,
103
- absolute: true
104
- })) {
105
- if (file.endsWith('.d.ts')) {
106
- continue;
107
- }
108
- const apiPath = relative(addonApiDir, file).replace(/\.(ts|js)$/, '');
109
- if (apiPath.indexOf('_') !== -1) continue;
110
-
111
- apis.push({
112
- file: file,
113
- routePrefix: `addon/${addon}`,
114
- displayName: `组件${addon}`
115
- });
116
- }
117
- }
118
-
119
- return apis;
120
- }
121
-
122
- /**
123
- * 初始化单个 API
124
- */
125
- async function initApi(apiRoutes: Map<string, ApiRoute>, apiInfo: { file: string; routePrefix: string; displayName: string }): Promise<void> {
126
- const { file, routePrefix, displayName } = apiInfo;
127
- const apiDir = routePrefix === '' ? projectApiDir : getAddonDir(routePrefix.replace('addon/', ''), 'apis');
128
- const apiPath = relative(apiDir, file).replace(/\.(ts|js)$/, '');
129
-
130
- try {
131
- // Windows 下路径需要转换为正斜杠格式
132
- const filePath = file.replace(/\\/g, '/');
133
- const apiImport = await import(filePath);
134
- const api = apiImport.default;
135
-
136
- // 设置默认值
137
- api.method = api.method || 'POST';
138
- api.auth = api.auth !== undefined ? api.auth : true;
139
- // 合并默认字段:默认字段作为基础,API 自定义字段优先级更高
140
- api.fields = { ...DEFAULT_API_FIELDS, ...(api.fields || {}) };
141
- api.required = api.required || [];
142
-
143
- // 构建路由
144
- api.route = `${api.method.toUpperCase()}/api/${routePrefix ? routePrefix + '/' : ''}${apiPath}`;
145
- apiRoutes.set(api.route, api);
146
- } catch (error: any) {
147
- Logger.error(`[${displayName}] 接口 ${apiPath} 加载失败`, error);
148
- process.exit(1);
149
- }
150
- }
151
-
152
59
  /**
153
60
  * 加载所有 API 路由
154
61
  * @param apiRoutes - API 路由映射表
@@ -157,19 +64,67 @@ export async function loadApis(apiRoutes: Map<string, ApiRoute>): Promise<void>
157
64
  try {
158
65
  const loadStartTime = Bun.nanoseconds();
159
66
 
160
- // 阶段1:扫描所有 API
161
- const userApis = await scanUserApis();
162
- const addonApis = await scanAddonApis();
163
-
164
- // 阶段2:初始化所有 API(用户 → 组件)
165
- // 2.1 初始化用户 APIs
166
- for (const apiInfo of userApis) {
167
- await initApi(apiRoutes, apiInfo);
67
+ // 1. 扫描项目 API
68
+ const projectApiFiles = await scanFiles(projectApiDir);
69
+ const projectApiList = projectApiFiles.map((file) => ({
70
+ filePath: file.filePath,
71
+ relativePath: file.relativePath,
72
+ type: 'project' as const,
73
+ routePrefix: '',
74
+ typeName: '项目'
75
+ }));
76
+
77
+ // 2. 扫描组件 API
78
+ const addonApiList: Array<{
79
+ filePath: string;
80
+ relativePath: string;
81
+ type: 'addon';
82
+ routePrefix: string;
83
+ typeName: string;
84
+ }> = [];
85
+ const addons = scanAddons();
86
+ for (const addon of addons) {
87
+ if (!addonDirExists(addon, 'apis')) continue;
88
+
89
+ const addonApiDir = getAddonDir(addon, 'apis');
90
+ const addonApiFiles = await scanFiles(addonApiDir);
91
+
92
+ for (const file of addonApiFiles) {
93
+ addonApiList.push({
94
+ filePath: file.filePath,
95
+ relativePath: file.relativePath,
96
+ type: 'addon' as const,
97
+ routePrefix: `addon/${addon}`,
98
+ typeName: `组件${addon}`
99
+ });
100
+ }
168
101
  }
169
102
 
170
- // 2.2 初始化组件 APIs
171
- for (const apiInfo of addonApis) {
172
- await initApi(apiRoutes, apiInfo);
103
+ // 3. 合并所有 API 文件
104
+ const allApiFiles = [...projectApiList, ...addonApiList];
105
+
106
+ // 4. 遍历处理所有 API 文件
107
+ for (const apiFile of allApiFiles) {
108
+ try {
109
+ // Windows 下路径需要转换为正斜杠格式
110
+ const normalizedFilePath = apiFile.filePath.replace(/\\/g, '/');
111
+ const apiImport = await import(normalizedFilePath);
112
+ const api = apiImport.default;
113
+
114
+ // 设置默认值
115
+ api.method = api.method || 'POST';
116
+ api.auth = api.auth !== undefined ? api.auth : true;
117
+ // 合并默认字段:默认字段作为基础,API 自定义字段优先级更高
118
+ api.fields = { ...DEFAULT_API_FIELDS, ...(api.fields || {}) };
119
+ api.required = api.required || [];
120
+
121
+ // 构建路由
122
+ api.route = `${api.method.toUpperCase()}/api/${apiFile.routePrefix ? apiFile.routePrefix + '/' : ''}${apiFile.relativePath}`;
123
+ apiRoutes.set(api.route, api);
124
+ } catch (error: any) {
125
+ Logger.error(`[${apiFile.typeName}] 接口 ${apiFile.relativePath} 加载失败`, error);
126
+ process.exit(1);
127
+ }
173
128
  }
174
129
 
175
130
  const totalLoadTime = calcPerfTime(loadStartTime);
@@ -0,0 +1,65 @@
1
+ /**
2
+ * 钩子加载器
3
+ * 负责扫描和初始化所有钩子(核心、组件、项目)
4
+ */
5
+
6
+ // 外部依赖
7
+ import { scanAddons, getAddonDir } from 'befly-util';
8
+
9
+ // 相对导入
10
+ import { Logger } from '../lib/logger.js';
11
+ import { coreHookDir, projectHookDir } from '../paths.js';
12
+ import { sortModules, scanModules } from '../util.js';
13
+
14
+ // 类型导入
15
+ import type { Hook } from '../types/hook.js';
16
+
17
+ export async function loadHooks(befly: {
18
+ //
19
+ hookLists: Hook[];
20
+ pluginsConfig?: Record<string, any>;
21
+ }): Promise<void> {
22
+ try {
23
+ const allHooks: Hook[] = [];
24
+
25
+ // 1. 扫描核心钩子
26
+ const coreHooks = await scanModules<Hook>(coreHookDir, 'core', '钩子', befly.pluginsConfig);
27
+
28
+ // 2. 扫描组件钩子
29
+ const addonHooks: Hook[] = [];
30
+ const addons = scanAddons();
31
+ for (const addon of addons) {
32
+ const dir = getAddonDir(addon, 'hooks');
33
+ const hooks = await scanModules<Hook>(dir, 'addon', '钩子', befly.pluginsConfig, addon);
34
+ addonHooks.push(...hooks);
35
+ }
36
+
37
+ // 3. 扫描项目钩子
38
+ const appHooks = await scanModules<Hook>(projectHookDir, 'app', '钩子', befly.pluginsConfig);
39
+
40
+ // 4. 合并所有钩子
41
+ allHooks.push(...coreHooks);
42
+ allHooks.push(...addonHooks);
43
+ allHooks.push(...appHooks);
44
+
45
+ // 5. 过滤禁用的钩子
46
+ const disableHooks = (befly as any).config?.disableHooks || [];
47
+ const enabledHooks = allHooks.filter((hook) => !disableHooks.includes(hook.name));
48
+
49
+ if (disableHooks.length > 0) {
50
+ Logger.info(`禁用钩子: ${disableHooks.join(', ')}`);
51
+ }
52
+
53
+ // 6. 排序
54
+ const sortedHooks = sortModules(enabledHooks);
55
+ if (sortedHooks === false) {
56
+ Logger.error('钩子依赖关系错误,请检查 after 属性');
57
+ process.exit(1);
58
+ }
59
+
60
+ befly.hookLists.push(...sortedHooks);
61
+ } catch (error: any) {
62
+ Logger.error('加载钩子时发生错误', error);
63
+ process.exit(1);
64
+ }
65
+ }