befly 3.0.0 → 3.1.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 (60) hide show
  1. package/checks/conflict.ts +35 -114
  2. package/checks/table.ts +31 -63
  3. package/config/env.ts +3 -3
  4. package/config/fields.ts +55 -0
  5. package/config/regexAliases.ts +51 -0
  6. package/config/reserved.ts +1 -1
  7. package/main.ts +17 -71
  8. package/package.json +7 -28
  9. package/plugins/db.ts +11 -10
  10. package/plugins/redis.ts +5 -9
  11. package/scripts/syncDb/apply.ts +3 -3
  12. package/scripts/syncDb/constants.ts +2 -1
  13. package/scripts/syncDb/ddl.ts +15 -8
  14. package/scripts/syncDb/helpers.ts +3 -2
  15. package/scripts/syncDb/index.ts +23 -35
  16. package/scripts/syncDb/state.ts +8 -6
  17. package/scripts/syncDb/table.ts +32 -22
  18. package/scripts/syncDb/tableCreate.ts +9 -3
  19. package/scripts/syncDb/tests/constants.test.ts +2 -1
  20. package/scripts/syncDb.ts +10 -9
  21. package/types/addon.d.ts +53 -0
  22. package/types/api.d.ts +17 -14
  23. package/types/befly.d.ts +2 -6
  24. package/types/context.d.ts +7 -0
  25. package/types/database.d.ts +9 -14
  26. package/types/index.d.ts +442 -8
  27. package/types/index.ts +35 -56
  28. package/types/redis.d.ts +2 -0
  29. package/types/validator.d.ts +0 -2
  30. package/types/validator.ts +43 -0
  31. package/utils/colors.ts +117 -37
  32. package/utils/database.ts +348 -0
  33. package/utils/dbHelper.ts +687 -116
  34. package/utils/helper.ts +812 -0
  35. package/utils/index.ts +10 -23
  36. package/utils/logger.ts +78 -171
  37. package/utils/redisHelper.ts +135 -152
  38. package/{types/context.ts → utils/requestContext.ts} +3 -3
  39. package/utils/sqlBuilder.ts +142 -165
  40. package/utils/validate.ts +51 -9
  41. package/apis/health/info.ts +0 -64
  42. package/apis/tool/tokenCheck.ts +0 -51
  43. package/bin/befly.ts +0 -202
  44. package/bunfig.toml +0 -3
  45. package/plugins/tool.ts +0 -34
  46. package/scripts/syncDev.ts +0 -112
  47. package/system.ts +0 -149
  48. package/tables/_common.json +0 -21
  49. package/tables/admin.json +0 -10
  50. package/utils/addonHelper.ts +0 -60
  51. package/utils/api.ts +0 -23
  52. package/utils/datetime.ts +0 -51
  53. package/utils/errorHandler.ts +0 -68
  54. package/utils/objectHelper.ts +0 -68
  55. package/utils/pluginHelper.ts +0 -62
  56. package/utils/response.ts +0 -38
  57. package/utils/sqlHelper.ts +0 -447
  58. package/utils/tableHelper.ts +0 -167
  59. package/utils/tool.ts +0 -230
  60. package/utils/typeHelper.ts +0 -101
package/utils/index.ts CHANGED
@@ -5,42 +5,29 @@
5
5
  * 保持向后兼容性,支持从 utils/index.js 导入所有工具函数
6
6
  */
7
7
 
8
- // ========== API 响应工具 ==========
9
- export { Yes, No } from './response.js';
8
+ // ========== 通用辅助工具(helpers.ts)==========
9
+ export { Yes, No, isDebug, isType, isEmptyObject, isEmptyArray, pickFields, formatDate, calcPerfTime, cleanData, toSnakeCase, toCamelCase, keysToSnake, keysToCamel } from './helper.js';
10
+ export type { DataCleanOptions } from './helper.js';
10
11
 
11
- // ========== 类型判断工具 ==========
12
- export { isType, isEmptyObject, isEmptyArray } from './typeHelper.js';
13
-
14
- // ========== 对象操作工具 ==========
15
- export { pickFields, omitFields } from './objectHelper.js';
16
-
17
- // ========== 日期时间工具 ==========
18
- export { formatDate, calcPerfTime } from './datetime.js';
12
+ // ========== 框架工具 ==========
13
+ export { scanAddons, getAddonDir, addonDirExists, sortPlugins, parseRule } from './helper.js';
19
14
 
20
15
  // ========== 数据库工具 ==========
21
- export { toSnakeTableName, buildDatabaseUrl, createSqlClient } from './dbHelper.js';
22
-
23
- // ========== 表定义工具 ==========
24
- export { parseRule, loadTableFields, pickTableFields } from './tableHelper.js';
25
-
26
- // ========== 插件系统工具 ==========
27
- export { sortPlugins } from './pluginHelper.js';
16
+ export { buildDatabaseUrl, createSqlClient, buildRedisUrl, createRedisClient, initDatabase, closeDatabase, initSqlOnly, initRedisOnly, getRedis, getSql, getDbHelper, isDatabaseInitialized } from './database.js';
28
17
 
29
- // 导出其他模块
18
+ // 导出其他大型模块
30
19
  export { Colors } from './colors.js';
31
20
  export { Logger } from './logger.js';
32
21
  export { Validator } from './validate.js';
33
22
  export { SqlBuilder } from './sqlBuilder.js';
34
- export { SqlHelper } from './sqlHelper.js';
35
- export { RedisHelper, initRedisClient, getRedisClient, closeRedisClient } from './redisHelper.js';
23
+ export { DbHelper } from './dbHelper.js';
24
+ export { RedisHelper } from './redisHelper.js';
36
25
  export { Jwt } from './jwt.js';
37
26
  export { Crypto2 } from './crypto.js';
38
27
  export { Xml } from './xml.js';
39
- export { Api } from './api.js';
40
- export { Tool } from './tool.js';
41
28
 
42
29
  // 类型导出
43
- export type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, TransactionCallback } from './sqlHelper.js';
30
+ export type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, TransactionCallback } from './dbHelper.js';
44
31
  export type { JwtPayload } from './jwt.js';
45
32
  export type { EncodingType, HashAlgorithm } from './crypto.js';
46
33
  export type { XmlParseOptions } from './xml.js';
package/utils/logger.ts CHANGED
@@ -6,19 +6,10 @@
6
6
  import path from 'path';
7
7
  import { appendFile, stat } from 'node:fs/promises';
8
8
  import { formatDate } from './index.js';
9
+ import { Colors } from './colors.js';
9
10
  import { Env } from '../config/env.js';
10
11
  import type { LogLevel } from '../types/common.js';
11
12
 
12
- /**
13
- * 日志级别映射
14
- */
15
- interface LogLevels {
16
- error: number;
17
- warn: number;
18
- info: number;
19
- debug: number;
20
- }
21
-
22
13
  /**
23
14
  * 日志消息类型
24
15
  */
@@ -28,17 +19,6 @@ type LogMessage = string | number | boolean | null | undefined | Record<string,
28
19
  * 日志器类
29
20
  */
30
21
  export class Logger {
31
- /** 当前日志级别 */
32
- static level: LogLevel = (Env.LOG_LEVEL as LogLevel) || 'info';
33
-
34
- /** 日志级别权重 */
35
- static readonly levels: LogLevels = {
36
- error: 0,
37
- warn: 1,
38
- info: 2,
39
- debug: 3
40
- };
41
-
42
22
  /** 日志目录 */
43
23
  static logDir: string = Env.LOG_DIR || 'logs';
44
24
 
@@ -49,55 +29,44 @@ export class Logger {
49
29
  private static currentFiles: Map<string, string> = new Map();
50
30
 
51
31
  /**
52
- * 格式化日志消息
32
+ * 记录日志
53
33
  * @param level - 日志级别
54
34
  * @param message - 日志消息
55
- * @returns 格式化后的日志字符串
56
35
  */
57
- static formatMessage(level: LogLevel, message: LogMessage): string {
58
- const timestamp = formatDate();
59
- const levelStr = level.toUpperCase().padStart(5);
36
+ static async log(level: LogLevel, message: LogMessage): Promise<void> {
37
+ // debug 日志特殊处理:仅当 LOG_DEBUG=1 时才记录
38
+ if (level === 'debug' && Env.LOG_DEBUG !== 1) return;
60
39
 
61
- let msg = `[${timestamp}] ${levelStr} - `;
40
+ // 格式化消息
41
+ const timestamp = formatDate();
42
+ const colorMap = {
43
+ info: Colors.greenBright,
44
+ debug: Colors.cyanBright,
45
+ warn: Colors.yellowBright,
46
+ error: Colors.redBright
47
+ };
62
48
 
63
- // 处理不同类型的消息
64
- if (typeof message === 'object' && message !== null) {
65
- if (Object.keys(message).length > 0) {
66
- msg += JSON.stringify(message).replace(/\s+/g, ' ').replace(/\\"/g, '"').replace(/\\n/g, ' ');
67
- }
49
+ // 处理消息内容
50
+ let content = '';
51
+ if (typeof message === 'object' && message !== null && Object.keys(message).length > 0) {
52
+ content = JSON.stringify(message, null, 0).replace(/\\"/g, '"');
68
53
  } else {
69
- msg += String(message);
54
+ content = String(message);
70
55
  }
71
56
 
72
- return msg;
73
- }
74
-
75
- /**
76
- * 检查是否应该记录该级别的日志
77
- * @param level - 日志级别
78
- * @returns 是否应该记录
79
- */
80
- private static shouldLog(level: LogLevel): boolean {
81
- return this.levels[level] <= this.levels[this.level];
82
- }
83
-
84
- /**
85
- * 记录日志
86
- * @param level - 日志级别
87
- * @param message - 日志消息
88
- */
89
- static async log(level: LogLevel, message: LogMessage): Promise<void> {
90
- // 检查日志级别
91
- if (!this.shouldLog(level)) return;
92
-
93
- const formattedMessage = this.formatMessage(level, message);
57
+ // 带颜色的控制台消息
58
+ const coloredLevelStr = colorMap[level](level.toUpperCase().padStart(5));
59
+ const coloredMessage = `[${timestamp}] ${coloredLevelStr} - ${content}`;
94
60
 
95
61
  // 控制台输出
96
62
  if (Env.LOG_TO_CONSOLE === 1) {
97
- console.log(formattedMessage);
63
+ console.log(coloredMessage);
98
64
  }
99
65
 
100
- await this.writeToFile(formattedMessage, level);
66
+ // 文件输出(去除 ANSI 颜色代码)
67
+ const plainLevelStr = level.toUpperCase().padStart(5);
68
+ const plainMessage = `[${timestamp}] ${plainLevelStr} - ${content}`;
69
+ await this.writeToFile(plainMessage, level);
101
70
  }
102
71
 
103
72
  /**
@@ -107,14 +76,8 @@ export class Logger {
107
76
  */
108
77
  static async writeToFile(message: string, level: LogLevel = 'info'): Promise<void> {
109
78
  try {
110
- let prefix: string;
111
-
112
- // debug 日志使用单独的文件名
113
- if (level === 'debug') {
114
- prefix = 'debug';
115
- } else {
116
- prefix = new Date().toISOString().split('T')[0];
117
- }
79
+ // 确定文件前缀
80
+ const prefix = level === 'debug' ? 'debug' : new Date().toISOString().split('T')[0];
118
81
 
119
82
  // 检查缓存的当前文件是否仍然可用
120
83
  let currentLogFile = this.currentFiles.get(prefix);
@@ -122,74 +85,76 @@ export class Logger {
122
85
  if (currentLogFile) {
123
86
  try {
124
87
  const stats = await stat(currentLogFile);
125
- // 如果文件超过最大大小,清除缓存
126
88
  if (stats.size >= this.maxFileSize) {
127
89
  this.currentFiles.delete(prefix);
128
90
  currentLogFile = undefined;
129
91
  }
130
- } catch (error) {
131
- // 文件不存在或无法访问,清除缓存
92
+ } catch {
132
93
  this.currentFiles.delete(prefix);
133
94
  currentLogFile = undefined;
134
95
  }
135
96
  }
136
97
 
137
- // 如果没有缓存的文件或文件已满,查找合适的文件
98
+ // 查找或创建新文件
138
99
  if (!currentLogFile) {
139
- currentLogFile = await this.findAvailableLogFile(prefix);
100
+ const glob = new Bun.Glob(`${prefix}.*.log`);
101
+ const files = await Array.fromAsync(glob.scan(this.logDir));
102
+
103
+ // 按索引排序并查找可用文件
104
+ const getIndex = (f: string) => parseInt(f.match(/\.(\d+)\.log$/)?.[1] || '0');
105
+ files.sort((a, b) => getIndex(a) - getIndex(b));
106
+
107
+ let foundFile = false;
108
+ for (let i = files.length - 1; i >= 0; i--) {
109
+ const filePath = path.join(this.logDir, files[i]);
110
+ try {
111
+ const stats = await stat(filePath);
112
+ if (stats.size < this.maxFileSize) {
113
+ currentLogFile = filePath;
114
+ foundFile = true;
115
+ break;
116
+ }
117
+ } catch {
118
+ continue;
119
+ }
120
+ }
121
+
122
+ // 没有可用文件,创建新文件
123
+ if (!foundFile) {
124
+ const maxIndex = files.length > 0 ? Math.max(...files.map(getIndex)) : -1;
125
+ currentLogFile = path.join(this.logDir, `${prefix}.${maxIndex + 1}.log`);
126
+ }
127
+
140
128
  this.currentFiles.set(prefix, currentLogFile);
141
129
  }
142
130
 
143
- // 使用 Node.js 的 appendFile 进行文件追加
144
131
  await appendFile(currentLogFile, message + '\n', 'utf8');
145
132
  } catch (error: any) {
146
- console.error('写入日志文件失败:', error.message);
133
+ console.error('写入日志文件失败:', error?.message || error);
147
134
  }
148
135
  }
149
136
 
150
137
  /**
151
- * 查找可用的日志文件
152
- * @param prefix - 文件前缀
153
- * @returns 可用的日志文件路径
138
+ * 记录错误日志
139
+ * @param name - 错误名称/位置
140
+ * @param error - 错误对象或消息
154
141
  */
155
- static async findAvailableLogFile(prefix: string): Promise<string> {
156
- const glob = new Bun.Glob(`${prefix}.*.log`);
157
- const files = await Array.fromAsync(glob.scan(this.logDir));
158
-
159
- // 按文件名排序
160
- files.sort((a, b) => {
161
- const aNum = parseInt(a.match(/\.(\d+)\.log$/)?.[1] || '0');
162
- const bNum = parseInt(b.match(/\.(\d+)\.log$/)?.[1] || '0');
163
- return aNum - bNum;
164
- });
165
-
166
- // 从最后一个文件开始检查
167
- for (let i = files.length - 1; i >= 0; i--) {
168
- const filePath = path.join(this.logDir, files[i]);
169
- try {
170
- const stats = await stat(filePath);
171
- if (stats.size < this.maxFileSize) {
172
- return filePath;
173
- }
174
- } catch (error) {
175
- // 文件不存在或无法访问,跳过
176
- continue;
177
- }
142
+ static async error(name: string, error?: any): Promise<void> {
143
+ if (!error) {
144
+ return this.log('error', name);
178
145
  }
179
146
 
180
- // 所有文件都已满或没有文件,创建新文件
181
- const existingIndices = files.map((f) => parseInt(f.match(/\.(\d+)\.log$/)?.[1] || '0'));
182
- const nextIndex = existingIndices.length > 0 ? Math.max(...existingIndices) + 1 : 0;
183
-
184
- return path.join(this.logDir, `${prefix}.${nextIndex}.log`);
185
- }
147
+ // 构建错误消息
148
+ const parts = [name];
149
+ if (error?.message || error?.stack) {
150
+ if (error.message) parts.push(error.message);
151
+ if (error.stack) parts.push('\n' + error.stack);
152
+ } else {
153
+ const errorStr = typeof error === 'object' ? JSON.stringify(error) : String(error);
154
+ parts.push(errorStr);
155
+ }
186
156
 
187
- /**
188
- * 记录错误日志
189
- * @param message - 日志消息
190
- */
191
- static async error(message: LogMessage): Promise<void> {
192
- await this.log('error', message);
157
+ await this.log('error', parts.join(' - '));
193
158
  }
194
159
 
195
160
  /**
@@ -209,52 +174,12 @@ export class Logger {
209
174
  }
210
175
 
211
176
  /**
212
- * 记录调试日志(总是记录,忽略级别检查)
177
+ * 记录调试日志
178
+ * 受 LOG_DEBUG 环境变量控制,仅当 LOG_DEBUG=1 时才记录
213
179
  * @param message - 日志消息
214
180
  */
215
181
  static async debug(message: LogMessage): Promise<void> {
216
- const formattedMessage = this.formatMessage('debug', message);
217
-
218
- // 控制台输出
219
- if (Env.LOG_TO_CONSOLE === 1) {
220
- console.log(formattedMessage);
221
- }
222
-
223
- await this.writeToFile(formattedMessage, 'debug');
224
- }
225
-
226
- /**
227
- * 设置日志级别
228
- * @param level - 新的日志级别
229
- */
230
- static setLevel(level: LogLevel): void {
231
- this.level = level;
232
- }
233
-
234
- /**
235
- * 获取当前日志级别
236
- * @returns 当前日志级别
237
- */
238
- static getLevel(): LogLevel {
239
- return this.level;
240
- }
241
-
242
- /**
243
- * 设置日志目录
244
- * @param dir - 新的日志目录
245
- */
246
- static setLogDir(dir: string): void {
247
- this.logDir = dir;
248
- // 清除文件缓存
249
- this.currentFiles.clear();
250
- }
251
-
252
- /**
253
- * 设置最大文件大小
254
- * @param size - 文件大小(字节)
255
- */
256
- static setMaxFileSize(size: number): void {
257
- this.maxFileSize = size;
182
+ await this.log('debug', message);
258
183
  }
259
184
 
260
185
  /**
@@ -263,22 +188,4 @@ export class Logger {
263
188
  static clearCache(): void {
264
189
  this.currentFiles.clear();
265
190
  }
266
-
267
- /**
268
- * 获取日志文件统计信息
269
- * @returns 日志文件统计
270
- */
271
- static getStats(): {
272
- level: LogLevel;
273
- logDir: string;
274
- maxFileSize: number;
275
- cachedFiles: number;
276
- } {
277
- return {
278
- level: this.level,
279
- logDir: this.logDir,
280
- maxFileSize: this.maxFileSize,
281
- cachedFiles: this.currentFiles.size
282
- };
283
- }
284
191
  }