befly 2.3.2 → 3.0.0

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 (93) hide show
  1. package/apis/health/info.ts +64 -0
  2. package/apis/tool/tokenCheck.ts +51 -0
  3. package/bin/befly.ts +202 -0
  4. package/checks/conflict.ts +408 -0
  5. package/checks/table.ts +284 -0
  6. package/config/env.ts +218 -0
  7. package/config/reserved.ts +96 -0
  8. package/main.ts +101 -0
  9. package/package.json +45 -16
  10. package/plugins/{db.js → db.ts} +25 -12
  11. package/plugins/logger.ts +28 -0
  12. package/plugins/redis.ts +51 -0
  13. package/plugins/tool.ts +34 -0
  14. package/scripts/syncDb/apply.ts +171 -0
  15. package/scripts/syncDb/constants.ts +70 -0
  16. package/scripts/syncDb/ddl.ts +182 -0
  17. package/scripts/syncDb/helpers.ts +172 -0
  18. package/scripts/syncDb/index.ts +215 -0
  19. package/scripts/syncDb/schema.ts +199 -0
  20. package/scripts/syncDb/sqlite.ts +50 -0
  21. package/scripts/syncDb/state.ts +104 -0
  22. package/scripts/syncDb/table.ts +204 -0
  23. package/scripts/syncDb/tableCreate.ts +142 -0
  24. package/scripts/syncDb/tests/constants.test.ts +104 -0
  25. package/scripts/syncDb/tests/ddl.test.ts +134 -0
  26. package/scripts/syncDb/tests/helpers.test.ts +70 -0
  27. package/scripts/syncDb/types.ts +92 -0
  28. package/scripts/syncDb/version.ts +73 -0
  29. package/scripts/syncDb.ts +9 -0
  30. package/scripts/syncDev.ts +112 -0
  31. package/system.ts +149 -0
  32. package/tables/_common.json +21 -0
  33. package/tables/admin.json +10 -0
  34. package/tsconfig.json +58 -0
  35. package/types/api.d.ts +246 -0
  36. package/types/befly.d.ts +234 -0
  37. package/types/common.d.ts +215 -0
  38. package/types/context.ts +167 -0
  39. package/types/crypto.d.ts +23 -0
  40. package/types/database.d.ts +278 -0
  41. package/types/index.d.ts +16 -0
  42. package/types/index.ts +459 -0
  43. package/types/jwt.d.ts +99 -0
  44. package/types/logger.d.ts +43 -0
  45. package/types/plugin.d.ts +109 -0
  46. package/types/redis.d.ts +44 -0
  47. package/types/tool.d.ts +67 -0
  48. package/types/validator.d.ts +45 -0
  49. package/utils/addonHelper.ts +60 -0
  50. package/utils/api.ts +23 -0
  51. package/utils/{colors.js → colors.ts} +79 -21
  52. package/utils/crypto.ts +308 -0
  53. package/utils/datetime.ts +51 -0
  54. package/utils/dbHelper.ts +142 -0
  55. package/utils/errorHandler.ts +68 -0
  56. package/utils/index.ts +46 -0
  57. package/utils/jwt.ts +493 -0
  58. package/utils/logger.ts +284 -0
  59. package/utils/objectHelper.ts +68 -0
  60. package/utils/pluginHelper.ts +62 -0
  61. package/utils/redisHelper.ts +338 -0
  62. package/utils/response.ts +38 -0
  63. package/utils/{sqlBuilder.js → sqlBuilder.ts} +233 -97
  64. package/utils/sqlHelper.ts +447 -0
  65. package/utils/tableHelper.ts +167 -0
  66. package/utils/tool.ts +230 -0
  67. package/utils/typeHelper.ts +101 -0
  68. package/utils/validate.ts +451 -0
  69. package/utils/{xml.js → xml.ts} +100 -74
  70. package/.npmrc +0 -3
  71. package/.prettierignore +0 -2
  72. package/.prettierrc +0 -11
  73. package/apis/health/info.js +0 -49
  74. package/apis/tool/tokenCheck.js +0 -29
  75. package/checks/table.js +0 -221
  76. package/config/env.js +0 -62
  77. package/main.js +0 -579
  78. package/plugins/logger.js +0 -14
  79. package/plugins/redis.js +0 -32
  80. package/plugins/tool.js +0 -8
  81. package/scripts/syncDb.js +0 -603
  82. package/system.js +0 -118
  83. package/tables/common.json +0 -16
  84. package/tables/tool.json +0 -6
  85. package/utils/api.js +0 -27
  86. package/utils/crypto.js +0 -260
  87. package/utils/index.js +0 -387
  88. package/utils/jwt.js +0 -387
  89. package/utils/logger.js +0 -143
  90. package/utils/redisHelper.js +0 -74
  91. package/utils/sqlManager.js +0 -471
  92. package/utils/tool.js +0 -31
  93. package/utils/validate.js +0 -228
package/utils/index.js DELETED
@@ -1,387 +0,0 @@
1
- import { fileURLToPath } from 'node:url';
2
- import path from 'node:path';
3
- import { SQL } from 'bun';
4
- import { Env } from '../config/env.js';
5
- import { Logger } from './logger.js';
6
-
7
- export const setCorsOptions = (req) => {
8
- return {
9
- headers: {
10
- 'Access-Control-Allow-Origin': Env.ALLOWED_ORIGIN || req.headers.get('origin') || '*',
11
- 'Access-Control-Allow-Methods': Env.ALLOWED_METHODS || 'GET, POST, PUT, DELETE, OPTIONS',
12
- 'Access-Control-Allow-Headers': Env.ALLOWED_HEADERS || 'Content-Type, Authorization, authorization, token',
13
- 'Access-Control-Expose-Headers': Env.EXPOSE_HEADERS || 'Content-Range, X-Content-Range, Authorization, authorization, token',
14
- 'Access-Control-Max-Age': Env.MAX_AGE || 86400,
15
- 'Access-Control-Allow-Credentials': Env.ALLOW_CREDENTIALS || 'true'
16
- }
17
- };
18
- };
19
-
20
- export const sortPlugins = (plugins) => {
21
- const result = [];
22
- const visited = new Set();
23
- const visiting = new Set();
24
- const pluginMap = Object.fromEntries(plugins.map((p) => [p.pluginName, p]));
25
- let isPass = true;
26
- const visit = (name) => {
27
- if (visited.has(name)) return;
28
- if (visiting.has(name)) {
29
- isPass = false;
30
- return;
31
- }
32
-
33
- const plugin = pluginMap[name];
34
- if (!plugin) return; // 依赖不存在时跳过
35
-
36
- visiting.add(name);
37
- (plugin.after || []).forEach(visit);
38
- visiting.delete(name);
39
- visited.add(name);
40
- result.push(plugin);
41
- };
42
-
43
- plugins.forEach((p) => visit(p.pluginName));
44
- return isPass ? result : false;
45
- };
46
-
47
- // 规则分割
48
- export const ruleSplit = (rule) => {
49
- const allParts = rule.split(',');
50
-
51
- // 如果部分数量小于等于5,直接返回
52
- if (allParts.length <= 5) {
53
- return allParts;
54
- }
55
-
56
- // 只取前4个部分,剩余的都合并为第5个部分
57
- return [allParts[0], allParts[1], allParts[2], allParts[3], allParts.slice(4).join(',')];
58
- };
59
-
60
- export const formatDate = (date = new Date(), format = 'YYYY-MM-DD HH:mm:ss') => {
61
- const d = new Date(date);
62
- const year = d.getFullYear();
63
- const month = String(d.getMonth() + 1).padStart(2, '0');
64
- const day = String(d.getDate()).padStart(2, '0');
65
- const hour = String(d.getHours()).padStart(2, '0');
66
- const minute = String(d.getMinutes()).padStart(2, '0');
67
- const second = String(d.getSeconds()).padStart(2, '0');
68
-
69
- return format.replace('YYYY', year).replace('MM', month).replace('DD', day).replace('HH', hour).replace('mm', minute).replace('ss', second);
70
- };
71
-
72
- /**
73
- * 计算时间差并返回带单位的字符串
74
- * @param {number} startTime - 开始时间(Bun.nanoseconds()返回值)
75
- * @param {number} endTime - 结束时间(可选,默认为当前时间)
76
- * @returns {string} 时间差(如果小于1秒返回"xx 毫秒",否则返回"xx 秒")
77
- */
78
- export const calcPerfTime = (startTime, endTime = Bun.nanoseconds()) => {
79
- const elapsedMs = (endTime - startTime) / 1_000_000;
80
-
81
- if (elapsedMs < 1000) {
82
- return `${elapsedMs.toFixed(2)} 毫秒`;
83
- } else {
84
- const elapsedSeconds = elapsedMs / 1000;
85
- return `${elapsedSeconds.toFixed(2)} 秒`;
86
- }
87
- };
88
-
89
- // 类型判断
90
- export const isType = (value, type) => {
91
- const actualType = Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
92
- const expectedType = String(type).toLowerCase();
93
-
94
- // 语义类型单独处理,其余走 actualType === expectedType
95
- switch (expectedType) {
96
- case 'function':
97
- // 统一将普通函数、异步函数、生成器函数等都识别为函数
98
- return typeof value === 'function';
99
- case 'nan':
100
- return typeof value === 'number' && Number.isNaN(value);
101
- case 'empty':
102
- return value === '' || value === null || value === undefined;
103
- case 'integer':
104
- return Number.isInteger(value);
105
- case 'float':
106
- return typeof value === 'number' && !Number.isInteger(value) && !Number.isNaN(value);
107
- case 'positive':
108
- return typeof value === 'number' && value > 0;
109
- case 'negative':
110
- return typeof value === 'number' && value < 0;
111
- case 'zero':
112
- return value === 0;
113
- case 'truthy':
114
- return !!value;
115
- case 'falsy':
116
- return !value;
117
- case 'primitive':
118
- return value !== Object(value);
119
- case 'reference':
120
- return value === Object(value);
121
- default:
122
- return actualType === expectedType;
123
- }
124
- };
125
-
126
- export const pickFields = (obj, keys) => {
127
- // 仅对对象或数组进行字段挑选,其他类型返回空对象(保持原有行为)
128
- if (!obj || (!isType(obj, 'object') && !isType(obj, 'array'))) {
129
- return {};
130
- }
131
-
132
- const result = {};
133
-
134
- for (const key of keys) {
135
- if (key in obj) {
136
- result[key] = obj[key];
137
- }
138
- }
139
-
140
- return result;
141
- };
142
-
143
- /**
144
- * 从对象或数组数据中按“字段名”和“字段值”进行排除过滤。
145
- * - 支持对象:移除指定字段名,以及值在排除值列表中的字段。
146
- * - 支持数组:
147
- * - 如果元素为对象,按同样规则清洗(移除字段名/字段值命中项)。
148
- * - 如果元素为原始值(数字/字符串等),当元素值命中排除值则从数组中移除该元素。
149
- *
150
- * 约定:excludeKeys 与 excludeValues 均为数组类型。
151
- *
152
- * 示例:
153
- * omitFields({ a:1, b:undefined, c:null }, ['a'], [undefined]) -> { c:null }
154
- * omitFields([{ a:1, b:null }, null, 0], ['a'], [null]) -> [{}, 0]
155
- *
156
- * 注意:仅当第一个参数为对象或数组时执行过滤,否则原样返回。
157
- *
158
- * @template T
159
- * @param {Record<string, any> | Array<any>} data - 原始数据(对象或数组)
160
- * @param {string[]} [excludeKeys=[]] - 要排除的字段名(对象属性名)数组
161
- * @param {any[]} [excludeValues=[]] - 要排除的字段值数组;当包含 undefined/null 等时,将移除这些值对应的字段或数组元素
162
- * @returns {T} 过滤后的数据,类型与入参保持一致
163
- */
164
- export const omitFields = (data, excludeKeys = [], excludeValues = []) => {
165
- const shouldDropValue = (v) => excludeValues.some((x) => x === v);
166
-
167
- const cleanObject = (obj) => {
168
- if (!isType(obj, 'object')) return obj;
169
- const result = {};
170
- for (const [k, v] of Object.entries(obj)) {
171
- if (excludeKeys.includes(k)) continue;
172
- if (shouldDropValue(v)) continue;
173
- result[k] = v;
174
- }
175
- return result;
176
- };
177
-
178
- if (isType(data, 'array')) {
179
- return /** @type {any} */ (data.filter((item) => !shouldDropValue(item)).map((item) => (isType(item, 'object') ? cleanObject(item) : item)));
180
- }
181
-
182
- if (isType(data, 'object')) {
183
- return /** @type {any} */ (cleanObject(data));
184
- }
185
-
186
- // 非对象/数组则原样返回(不处理)
187
- return /** @type {any} */ (data);
188
- };
189
-
190
- export const isEmptyObject = (obj) => {
191
- // 首先检查是否为对象
192
- if (!isType(obj, 'object')) {
193
- return false;
194
- }
195
-
196
- // 检查是否为空对象
197
- return Object.keys(obj).length === 0;
198
- };
199
-
200
- export const isEmptyArray = (arr) => {
201
- // 首先检查是否为数组
202
- if (!isType(arr, 'array')) {
203
- return false;
204
- }
205
-
206
- // 检查是否为空数组
207
- return arr.length === 0;
208
- };
209
-
210
- // 返回结果
211
- export const RYes = (msg = '', data = {}, other = {}) => {
212
- return {
213
- ...other,
214
- code: 0,
215
- msg: msg,
216
- data: data
217
- };
218
- };
219
-
220
- export const RNo = (msg = '', data = {}, other = {}) => {
221
- return {
222
- ...other,
223
- code: 1,
224
- msg: msg,
225
- data: data
226
- };
227
- };
228
-
229
- export const filename2 = (importMetaUrl) => {
230
- return fileURLToPath(importMetaUrl);
231
- };
232
-
233
- export const dirname2 = (importMetaUrl) => {
234
- return path.dirname(fileURLToPath(importMetaUrl));
235
- };
236
-
237
- // 过滤日志字段的函数
238
- export const filterLogFields = (body, excludeFields = '') => {
239
- // 仅在对象或数组时进行过滤,保持与原 typeof === 'object' 行为一致(数组也会进入)
240
- if (!body || (!isType(body, 'object') && !isType(body, 'array'))) return body;
241
-
242
- // 如果是字符串,按逗号分割并清理空格
243
- const fieldsArray = excludeFields
244
- .split(',')
245
- .map((field) => field.trim())
246
- .filter((field) => field.length > 0);
247
-
248
- // 创建新对象,只包含不在排除列表中的字段
249
- const filtered = {};
250
- for (const [key, value] of Object.entries(body)) {
251
- if (!fieldsArray.includes(key)) {
252
- filtered[key] = value;
253
- }
254
- }
255
- return filtered;
256
- };
257
-
258
- // 验证字段名称:中文、数字、字母、空格、下划线、短横线
259
- export const validateFieldName = (name) => {
260
- const nameRegex = /^[\u4e00-\u9fa5a-zA-Z0-9 _-]+$/;
261
- return nameRegex.test(name);
262
- };
263
-
264
- // 验证字段类型是否为指定的四种类型之一
265
- export const validateFieldType = (type) => {
266
- const validTypes = ['string', 'number', 'text', 'array'];
267
- return validTypes.includes(type);
268
- };
269
-
270
- // 验证最小值/最大值是否为null或数字
271
- export const validateMinMax = (value) => {
272
- return value === 'null' || (!isNaN(parseFloat(value)) && isFinite(parseFloat(value)));
273
- };
274
-
275
- // 验证默认值是否为null、字符串或数字
276
- export const validateDefaultValue = (value) => {
277
- if (value === 'null') return true;
278
- // 检查是否为数字
279
- if (!isNaN(parseFloat(value)) && isFinite(parseFloat(value))) return true;
280
- // 其他情况视为字符串,都是有效的
281
- return true;
282
- };
283
-
284
- // 验证索引标识是否为0或1
285
- export const validateIndex = (value) => {
286
- return value === '0' || value === '1';
287
- };
288
-
289
- // 验证正则表达式是否有效
290
- export const validateRegex = (value) => {
291
- if (value === 'null') return true;
292
- try {
293
- new RegExp(value);
294
- return true;
295
- } catch (e) {
296
- return false;
297
- }
298
- };
299
-
300
- // 将 lowerCamelCase 或单词形式转换为下划线风格(snake_case)
301
- // 例如:userTable -> user_table, testNewFormat -> test_new_format, users -> users, orderV2 -> order_v2
302
- export const toSnakeTableName = (name) => {
303
- if (!name) return name;
304
- return String(name)
305
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
306
- .replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, '$1_$2')
307
- .toLowerCase();
308
- };
309
-
310
- // 专门用于处理⚡分隔的字段规则
311
- export const parseFieldRule = (rule) => {
312
- const allParts = rule.split('⚡');
313
-
314
- // 必须包含7个部分:显示名⚡类型⚡最小值⚡最大值⚡默认值⚡是否索引⚡正则约束
315
- if (allParts.length !== 7) {
316
- throw new Error(`字段规则格式错误,必须包含7个部分,当前包含${allParts.length}个部分`);
317
- }
318
-
319
- // 验证各个部分的格式
320
- const [name, type, minValue, maxValue, defaultValue, isIndex, regexConstraint] = allParts;
321
-
322
- // 第1个值:名称必须为中文、数字、字母
323
- if (!validateFieldName(name)) {
324
- throw new Error(`字段名称 "${name}" 格式错误,必须为中文、数字、字母`);
325
- }
326
-
327
- // 第2个值:字段类型必须为string,number,text,array之一
328
- if (!validateFieldType(type)) {
329
- throw new Error(`字段类型 "${type}" 格式错误,必须为string、number、text、array之一`);
330
- }
331
-
332
- // 第3个值:最小值必须为null或数字
333
- if (!validateMinMax(minValue)) {
334
- throw new Error(`最小值 "${minValue}" 格式错误,必须为null或数字`);
335
- }
336
-
337
- // 第4个值:最大值必须为null或数字
338
- if (!validateMinMax(maxValue)) {
339
- throw new Error(`最大值 "${maxValue}" 格式错误,必须为null或数字`);
340
- }
341
-
342
- // 第5个值:默认值必须为null、字符串或数字
343
- if (!validateDefaultValue(defaultValue)) {
344
- throw new Error(`默认值 "${defaultValue}" 格式错误,必须为null、字符串或数字`);
345
- }
346
-
347
- // 第6个值:是否创建索引必须为0或1
348
- if (!validateIndex(isIndex)) {
349
- throw new Error(`索引标识 "${isIndex}" 格式错误,必须为0或1`);
350
- }
351
-
352
- // 第7个值:必须为null或正则表达式
353
- if (!validateRegex(regexConstraint)) {
354
- throw new Error(`正则约束 "${regexConstraint}" 格式错误,必须为null或有效的正则表达式`);
355
- }
356
-
357
- return allParts;
358
- };
359
-
360
- /**
361
- * 创建并校验 Bun SQL 客户端
362
- * - 否则按 scripts/syncDb.js 的方式拼接 URL
363
- * - 连接成功后返回 SQL 实例,失败会自动 close 并抛出
364
- * @param {object} options 传给 new SQL 的参数(如 { max: 1, bigint: true })
365
- */
366
- export async function createSqlClient(options = {}) {
367
- const url = `mysql://${encodeURIComponent(Env.MYSQL_USER)}:${encodeURIComponent(Env.MYSQL_PASSWORD)}@${Env.MYSQL_HOST}:${Env.MYSQL_PORT}/${Env.MYSQL_DB}`;
368
-
369
- const sql = new SQL({
370
- url: url,
371
- max: options.max ?? 1,
372
- bigint: options.bigint ?? true,
373
- ...options
374
- });
375
- try {
376
- const ver = await sql`SELECT VERSION() AS version`;
377
- const version = ver?.[0]?.version;
378
- Logger.info(`数据库连接成功,MySQL 版本: ${version}`);
379
- return sql;
380
- } catch (error) {
381
- Logger.error('数据库连接测试失败:', error);
382
- try {
383
- await sql.close();
384
- } catch {}
385
- throw error;
386
- }
387
- }