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
@@ -0,0 +1,447 @@
1
+ /**
2
+ * SQL 助手 - TypeScript 版本
3
+ * 提供数据库 CRUD 操作的封装
4
+ */
5
+
6
+ import { SqlBuilder } from './sqlBuilder.js';
7
+ import type { WhereConditions } from '../types/common.js';
8
+ import type { BeflyContext } from '../types/befly.js';
9
+ import type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, TransactionCallback } from '../types/database.js';
10
+
11
+ /**
12
+ * SQL 助手类
13
+ */
14
+ export class SqlHelper {
15
+ private befly: BeflyContext;
16
+ private sql: any = null;
17
+ private isTransaction: boolean = false;
18
+
19
+ /**
20
+ * 构造函数
21
+ * @param befly - Befly 上下文
22
+ * @param sql - Bun SQL 客户端(可选,用于事务)
23
+ */
24
+ constructor(befly: BeflyContext, sql: any = null) {
25
+ this.befly = befly;
26
+ this.sql = sql;
27
+ this.isTransaction = !!sql;
28
+ }
29
+
30
+ /**
31
+ * 处理插入数据(强制生成系统字段,用户不可覆盖)
32
+ */
33
+ private async processDataForInsert(data: Record<string, any>): Promise<Record<string, any>> {
34
+ // 复制用户数据,但移除系统字段(防止用户尝试覆盖)
35
+ const { id, created_at, updated_at, deleted_at, state, ...userData } = data;
36
+
37
+ const processed: Record<string, any> = { ...userData };
38
+
39
+ // 强制生成 ID(不可被用户覆盖)
40
+ try {
41
+ processed.id = await this.befly.redis.genTimeID();
42
+ } catch (error: any) {
43
+ throw new Error(`生成 ID 失败,Redis 可能不可用:${error.message}`);
44
+ }
45
+
46
+ // 强制生成时间戳(不可被用户覆盖)
47
+ const now = Date.now();
48
+ processed.created_at = now;
49
+ processed.updated_at = now;
50
+
51
+ // 强制设置 state 为 1(激活状态,不可被用户覆盖)
52
+ processed.state = 1;
53
+
54
+ // 注意:deleted_at 字段不在插入时生成,只在软删除时设置
55
+
56
+ return processed;
57
+ }
58
+
59
+ /**
60
+ * 添加默认 state 过滤(排除已删除数据)
61
+ */
62
+ private addDefaultStateFilter(where: WhereConditions | undefined, includeDeleted: boolean = false, customState?: WhereConditions): WhereConditions {
63
+ if (includeDeleted) {
64
+ return where || {};
65
+ }
66
+
67
+ // 如果有自定义 state 条件,使用自定义条件
68
+ if (customState) {
69
+ return where ? { ...where, ...customState } : customState;
70
+ }
71
+
72
+ // 检查用户是否已经在 where 中指定了 state 条件
73
+ if (where && 'state' in where) {
74
+ // 用户已指定 state 条件,直接返回,不覆盖
75
+ return where;
76
+ }
77
+
78
+ // 默认排除已删除(state = 0)
79
+ const stateFilter: WhereConditions = { state: { $gt: 0 } };
80
+ return where ? { ...where, ...stateFilter } : stateFilter;
81
+ }
82
+
83
+ /**
84
+ * 执行 SQL(使用 sql.unsafe,带慢查询日志)
85
+ */
86
+ private async executeWithConn(sqlStr: string, params?: any[]): Promise<any> {
87
+ if (!this.sql) {
88
+ throw new Error('数据库连接未初始化');
89
+ }
90
+
91
+ // 记录开始时间
92
+ const startTime = Date.now();
93
+
94
+ // 使用 sql.unsafe 执行查询
95
+ let result;
96
+ if (params && params.length > 0) {
97
+ result = await this.sql.unsafe(sqlStr, params);
98
+ } else {
99
+ result = await this.sql.unsafe(sqlStr);
100
+ }
101
+
102
+ // 计算执行时间
103
+ const duration = Date.now() - startTime;
104
+
105
+ // 慢查询警告(超过 1000ms)
106
+ if (duration > 1000) {
107
+ const sqlPreview = sqlStr.length > 100 ? sqlStr.substring(0, 100) + '...' : sqlStr;
108
+ console.warn(`🐌 检测到慢查询 (${duration}ms): ${sqlPreview}`);
109
+ }
110
+
111
+ return result;
112
+ }
113
+
114
+ /**
115
+ * 查询单条数据
116
+ */
117
+ async getDetail<T = any>(options: QueryOptions): Promise<T | null> {
118
+ const { table, fields = ['*'], where, includeDeleted = false, customState } = options;
119
+
120
+ const builder = new SqlBuilder().select(fields).from(table).where(this.addDefaultStateFilter(where, includeDeleted, customState)).limit(1);
121
+
122
+ const { sql, params } = builder.toSelectSql();
123
+ const result = await this.executeWithConn(sql, params);
124
+
125
+ return result?.[0] || null;
126
+ }
127
+
128
+ /**
129
+ * 查询列表(带分页)
130
+ */
131
+ async getList<T = any>(options: QueryOptions): Promise<ListResult<T>> {
132
+ const { table, fields = ['*'], where, orderBy = [], page = 1, limit = 10, includeDeleted = false, customState } = options;
133
+
134
+ // P1: 添加参数上限校验
135
+ if (page < 1 || page > 10000) {
136
+ throw new Error('页码必须在 1 到 10000 之间');
137
+ }
138
+ if (limit < 1 || limit > 1000) {
139
+ throw new Error('每页数量必须在 1 到 1000 之间');
140
+ }
141
+
142
+ // 构建查询
143
+ const whereFiltered = this.addDefaultStateFilter(where, includeDeleted, customState);
144
+
145
+ // 查询总数
146
+ const countBuilder = new SqlBuilder().select(['COUNT(*) as total']).from(table).where(whereFiltered);
147
+
148
+ const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
149
+ const countResult = await this.executeWithConn(countSql, countParams);
150
+ const total = countResult?.[0]?.total || 0;
151
+
152
+ // P1: 如果总数为 0,直接返回,不执行第二次查询
153
+ if (total === 0) {
154
+ return {
155
+ list: [],
156
+ total: 0,
157
+ page,
158
+ limit,
159
+ pages: 0
160
+ };
161
+ }
162
+
163
+ // 查询数据
164
+ const offset = (page - 1) * limit;
165
+ const dataBuilder = new SqlBuilder().select(fields).from(table).where(whereFiltered).limit(limit).offset(offset);
166
+
167
+ // P1: 只有用户明确指定了 orderBy 才添加排序
168
+ if (orderBy && orderBy.length > 0) {
169
+ dataBuilder.orderBy(orderBy);
170
+ }
171
+
172
+ const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
173
+ const list = (await this.executeWithConn(dataSql, dataParams)) || [];
174
+
175
+ return {
176
+ list,
177
+ total,
178
+ page,
179
+ limit,
180
+ pages: Math.ceil(total / limit)
181
+ };
182
+ }
183
+
184
+ /**
185
+ * 查询所有数据(不分页,有上限保护)
186
+ * ⚠️ 警告:此方法会查询大量数据,建议使用 getList 分页查询
187
+ */
188
+ async getAll<T = any>(options: Omit<QueryOptions, 'page' | 'limit'>): Promise<T[]> {
189
+ const { table, fields = ['*'], where, orderBy, includeDeleted = false, customState } = options;
190
+
191
+ // 添加硬性上限保护,防止内存溢出
192
+ const MAX_LIMIT = 10000;
193
+ const WARNING_LIMIT = 1000;
194
+
195
+ const builder = new SqlBuilder().select(fields).from(table).where(this.addDefaultStateFilter(where, includeDeleted, customState)).limit(MAX_LIMIT); // 强制添加上限
196
+
197
+ if (orderBy) {
198
+ builder.orderBy(orderBy);
199
+ }
200
+
201
+ const { sql, params } = builder.toSelectSql();
202
+ const result = (await this.executeWithConn(sql, params)) || [];
203
+
204
+ // 警告日志:返回数据超过警告阈值
205
+ if (result.length >= WARNING_LIMIT) {
206
+ console.warn(`⚠️ getAll 从表 \`${table}\` 返回了 ${result.length} 行数据,建议使用 getList 分页查询以获得更好的性能。`);
207
+ }
208
+
209
+ // 如果达到上限,额外警告
210
+ if (result.length >= MAX_LIMIT) {
211
+ console.warn(`🚨 getAll 达到了最大限制 (${MAX_LIMIT}),可能还有更多数据。请使用 getList 分页查询。`);
212
+ }
213
+
214
+ return result;
215
+ }
216
+
217
+ /**
218
+ * 插入数据(自动生成 ID、时间戳、state)
219
+ */
220
+ async insData(options: InsertOptions): Promise<number> {
221
+ const { table, data } = options;
222
+
223
+ // 处理数据(自动添加必要字段)
224
+ const processed = await this.processDataForInsert(data);
225
+
226
+ // 构建 SQL
227
+ const builder = new SqlBuilder();
228
+ const { sql, params } = builder.toInsertSql(table, processed);
229
+
230
+ // 执行
231
+ const result = await this.executeWithConn(sql, params);
232
+ return processed.id || result?.lastInsertRowid || 0;
233
+ }
234
+
235
+ /**
236
+ * 批量插入数据(真正的批量操作)
237
+ * 使用 INSERT INTO ... VALUES (...), (...), (...) 语法
238
+ * 自动生成系统字段并包装在事务中
239
+ */
240
+ async insDataBatch(table: string, dataList: Record<string, any>[]): Promise<number[]> {
241
+ // 空数组直接返回
242
+ if (dataList.length === 0) {
243
+ return [];
244
+ }
245
+
246
+ // 限制批量大小
247
+ const MAX_BATCH_SIZE = 1000;
248
+ if (dataList.length > MAX_BATCH_SIZE) {
249
+ throw new Error(`批量插入数量 ${dataList.length} 超过最大限制 ${MAX_BATCH_SIZE},请分批插入。`);
250
+ }
251
+
252
+ // 批量生成 ID(一次性从 Redis 获取 N 个 ID)
253
+ const ids = await this.befly.redis.genTimeIDBatch(dataList.length);
254
+ const now = Date.now();
255
+
256
+ // 处理所有数据(自动添加系统字段)
257
+ const processedList = dataList.map((data, index) => {
258
+ // 移除系统字段(防止用户尝试覆盖)
259
+ const { id, created_at, updated_at, deleted_at, state, ...userData } = data;
260
+
261
+ // 强制生成系统字段(不可被用户覆盖)
262
+ return {
263
+ ...userData,
264
+ id: ids[index],
265
+ created_at: now,
266
+ updated_at: now,
267
+ state: 1
268
+ };
269
+ });
270
+
271
+ // 构建批量插入 SQL
272
+ const builder = new SqlBuilder();
273
+ const { sql, params } = builder.toInsertSql(table, processedList);
274
+
275
+ // 在事务中执行批量插入
276
+ try {
277
+ await this.executeWithConn(sql, params);
278
+ return ids;
279
+ } catch (error: any) {
280
+ // 批量插入失败,记录错误
281
+ console.error(`表 \`${table}\` 批量插入失败:`, error.message);
282
+ throw error;
283
+ }
284
+ }
285
+
286
+ /**
287
+ * 更新数据(强制更新时间戳,系统字段不可修改)
288
+ */
289
+ async updData(options: UpdateOptions): Promise<number> {
290
+ const { table, data, where, includeDeleted = false } = options;
291
+
292
+ // 移除系统字段(防止用户尝试修改)
293
+ const { id, created_at, updated_at, deleted_at, state, ...userData } = data;
294
+
295
+ // 强制更新时间戳(不可被用户覆盖)
296
+ const processed: Record<string, any> = {
297
+ ...userData,
298
+ updated_at: Date.now()
299
+ };
300
+
301
+ // 构建 SQL
302
+ const whereFiltered = this.addDefaultStateFilter(where, includeDeleted);
303
+ const builder = new SqlBuilder().where(whereFiltered);
304
+ const { sql, params } = builder.toUpdateSql(table, processed);
305
+
306
+ // 执行
307
+ const result = await this.executeWithConn(sql, params);
308
+ return result?.changes || 0;
309
+ }
310
+
311
+ /**
312
+ * 删除数据(软删除会记录删除时间)
313
+ */
314
+ async delData(options: DeleteOptions): Promise<number> {
315
+ const { table, where, hard = false } = options;
316
+
317
+ if (hard) {
318
+ // 物理删除
319
+ const builder = new SqlBuilder().where(where);
320
+ const { sql, params } = builder.toDeleteSql(table);
321
+
322
+ const result = await this.executeWithConn(sql, params);
323
+ return result?.changes || 0;
324
+ } else {
325
+ // 软删除(设置 state=0 并记录删除时间)
326
+ const now = Date.now();
327
+ const data: Record<string, any> = {
328
+ state: 0,
329
+ updated_at: now,
330
+ deleted_at: now // 记录删除时间
331
+ };
332
+
333
+ return await this.updData({
334
+ table,
335
+ data,
336
+ where,
337
+ includeDeleted: true // 软删除时允许操作已删除数据
338
+ });
339
+ }
340
+ }
341
+
342
+ /**
343
+ * 执行事务
344
+ */
345
+ async trans<T = any>(callback: TransactionCallback<T>): Promise<T> {
346
+ if (this.isTransaction) {
347
+ // 已经在事务中,直接执行回调
348
+ return await callback(this);
349
+ }
350
+
351
+ // 开启新事务
352
+ const conn = await this.befly.db.transaction();
353
+
354
+ try {
355
+ const trans = new SqlHelper(this.befly, conn);
356
+ const result = await callback(trans);
357
+ await conn.query('COMMIT');
358
+ return result;
359
+ } catch (error) {
360
+ await conn.query('ROLLBACK');
361
+ throw error;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * 执行原始 SQL
367
+ */
368
+ async query(sql: string, params?: any[]): Promise<any> {
369
+ return await this.executeWithConn(sql, params);
370
+ }
371
+
372
+ /**
373
+ * 检查数据是否存在(优化性能)
374
+ */
375
+ async exists(options: Omit<QueryOptions, 'fields' | 'orderBy' | 'page' | 'limit'>): Promise<boolean> {
376
+ const { table, where, includeDeleted = false, customState } = options;
377
+
378
+ // 使用 COUNT(1) 性能更好
379
+ const builder = new SqlBuilder().select(['COUNT(1) as cnt']).from(table).where(this.addDefaultStateFilter(where, includeDeleted, customState)).limit(1);
380
+
381
+ const { sql, params } = builder.toSelectSql();
382
+ const result = await this.executeWithConn(sql, params);
383
+
384
+ return (result?.[0]?.cnt || 0) > 0;
385
+ }
386
+
387
+ /**
388
+ * 查询单个字段值(带字段名验证)
389
+ */
390
+ async getFieldValue<T = any>(options: Omit<QueryOptions, 'fields'> & { field: string }): Promise<T | null> {
391
+ const { field, ...queryOptions } = options;
392
+
393
+ // 验证字段名格式(只允许字母、数字、下划线)
394
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
395
+ throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线。`);
396
+ }
397
+
398
+ const result = await this.getDetail({
399
+ ...queryOptions,
400
+ fields: [field]
401
+ });
402
+
403
+ return result ? result[field] : null;
404
+ }
405
+
406
+ /**
407
+ * 自增字段(安全实现,防止 SQL 注入)
408
+ */
409
+ async increment(table: string, field: string, where: WhereConditions, value: number = 1): Promise<number> {
410
+ // 验证表名格式(只允许字母、数字、下划线)
411
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
412
+ throw new Error(`无效的表名: ${table}`);
413
+ }
414
+
415
+ // 验证字段名格式(只允许字母、数字、下划线)
416
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
417
+ throw new Error(`无效的字段名: ${field}`);
418
+ }
419
+
420
+ // 验证 value 必须是数字
421
+ if (typeof value !== 'number' || isNaN(value)) {
422
+ throw new Error(`自增值必须是有效的数字`);
423
+ }
424
+
425
+ // 使用 SqlBuilder 构建安全的 WHERE 条件
426
+ const whereFiltered = this.addDefaultStateFilter(where, false);
427
+ const builder = new SqlBuilder().where(whereFiltered);
428
+ const { sql: selectSql, params: whereParams } = builder.toSelectSql();
429
+
430
+ // 提取 WHERE 子句(找到 WHERE 关键字后的部分)
431
+ const whereIndex = selectSql.indexOf('WHERE');
432
+ const whereClause = whereIndex > -1 ? selectSql.substring(whereIndex + 6).trim() : '1=1';
433
+
434
+ // 构建安全的 UPDATE SQL(表名和字段名使用反引号转义)
435
+ const sql = `UPDATE \`${table}\` SET \`${field}\` = \`${field}\` + ? WHERE ${whereClause}`;
436
+
437
+ const result = await this.executeWithConn(sql, [value, ...whereParams]);
438
+ return result?.changes || 0;
439
+ }
440
+
441
+ /**
442
+ * 自减字段
443
+ */
444
+ async decrement(table: string, field: string, where: WhereConditions, value: number = 1): Promise<number> {
445
+ return await this.increment(table, field, where, -value);
446
+ }
447
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Befly 表定义工具
3
+ * 提供表字段规则解析功能
4
+ */
5
+
6
+ import path from 'node:path';
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { getProjectDir } from '../system.js';
9
+ import { getAddonDir } from './addonHelper.js';
10
+ import type { ParsedFieldRule } from '../types/common.js';
11
+
12
+ /**
13
+ * 解析字段规则字符串(以 | 分隔)
14
+ * 用于解析表定义中的字段规则
15
+ *
16
+ * 规则格式:字段名|类型|最小值|最大值|默认值|索引|正则
17
+ * 注意:只分割前6个|,第7个|之后的所有内容(包括|)都属于正则表达式
18
+ *
19
+ * @param rule - 字段规则字符串
20
+ * @returns 解析后的字段规则对象
21
+ *
22
+ * @example
23
+ * parseRule('用户名|string|2|50|null|1|null')
24
+ * // {
25
+ * // name: '用户名',
26
+ * // type: 'string',
27
+ * // min: 2,
28
+ * // max: 50,
29
+ * // default: 'null',
30
+ * // index: 1,
31
+ * // regex: null
32
+ * // }
33
+ *
34
+ * parseRule('年龄|number|0|150|18|0|null')
35
+ * // {
36
+ * // name: '年龄',
37
+ * // type: 'number',
38
+ * // min: 0,
39
+ * // max: 150,
40
+ * // default: 18,
41
+ * // index: 0,
42
+ * // regex: null
43
+ * // }
44
+ *
45
+ * parseRule('状态|string|1|20|active|1|^(active|inactive|pending)$')
46
+ * // 正则表达式中的 | 会被保留
47
+ */
48
+ export const parseRule = (rule: string): ParsedFieldRule => {
49
+ // 手动分割前6个|,剩余部分作为正则表达式
50
+ // 这样可以确保正则表达式中的|不会被分割
51
+ const parts: string[] = [];
52
+ let currentPart = '';
53
+ let pipeCount = 0;
54
+
55
+ for (let i = 0; i < rule.length; i++) {
56
+ if (rule[i] === '|' && pipeCount < 6) {
57
+ parts.push(currentPart);
58
+ currentPart = '';
59
+ pipeCount++;
60
+ } else {
61
+ currentPart += rule[i];
62
+ }
63
+ }
64
+ // 添加最后一部分(正则表达式)
65
+ parts.push(currentPart);
66
+
67
+ const [fieldName = '', fieldType = 'string', fieldMinStr = 'null', fieldMaxStr = 'null', fieldDefaultStr = 'null', fieldIndexStr = '0', fieldRegx = 'null'] = parts;
68
+
69
+ const fieldIndex = Number(fieldIndexStr) as 0 | 1;
70
+ const fieldMin = fieldMinStr !== 'null' ? Number(fieldMinStr) : null;
71
+ const fieldMax = fieldMaxStr !== 'null' ? Number(fieldMaxStr) : null;
72
+
73
+ let fieldDefault: any = fieldDefaultStr;
74
+ if (fieldType === 'number' && fieldDefaultStr !== 'null') {
75
+ fieldDefault = Number(fieldDefaultStr);
76
+ }
77
+
78
+ return {
79
+ name: fieldName,
80
+ type: fieldType as 'string' | 'number' | 'text' | 'array',
81
+ min: fieldMin,
82
+ max: fieldMax,
83
+ default: fieldDefault,
84
+ index: fieldIndex,
85
+ regex: fieldRegx !== 'null' ? fieldRegx : null
86
+ };
87
+ };
88
+
89
+ /**
90
+ * 从表定义中加载字段
91
+ * @param tableName - 表名
92
+ * @param options - 可选配置
93
+ * @returns 字段定义对象
94
+ */
95
+ export function loadTableFields(
96
+ tableName: string,
97
+ options?: {
98
+ source?: 'core' | 'project' | 'addon';
99
+ addonName?: string;
100
+ }
101
+ ): Record<string, string> {
102
+ const source = options?.source || 'project';
103
+ const addonName = options?.addonName || '';
104
+
105
+ let tableFilePath: string;
106
+
107
+ // 根据来源确定表定义文件路径
108
+ if (source === 'core') {
109
+ tableFilePath = path.join(import.meta.dir, '../tables', `${tableName}.json`);
110
+ } else if (source === 'addon') {
111
+ if (!addonName) {
112
+ throw new Error('addon 来源必须提供 addonName 参数');
113
+ }
114
+ tableFilePath = path.join(getAddonDir(addonName, 'tables'), `${tableName}.json`);
115
+ } else {
116
+ tableFilePath = path.join(getProjectDir('tables'), `${tableName}.json`);
117
+ }
118
+
119
+ // 检查文件是否存在
120
+ if (!existsSync(tableFilePath)) {
121
+ throw new Error(`表定义文件不存在: ${tableFilePath}`);
122
+ }
123
+
124
+ try {
125
+ // 读取并解析 JSON 文件
126
+ const fileContent = readFileSync(tableFilePath, 'utf-8');
127
+ const fields = JSON.parse(fileContent);
128
+
129
+ // 验证是否为对象
130
+ if (typeof fields !== 'object' || fields === null || Array.isArray(fields)) {
131
+ throw new Error(`表定义文件格式错误: ${tableFilePath}`);
132
+ }
133
+
134
+ return fields;
135
+ } catch (error: any) {
136
+ throw new Error(`加载表定义失败 (${tableName}): ${error.message}`);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * 从表定义中选择部分字段
142
+ * @param tableName - 表名
143
+ * @param fieldNames - 要选择的字段名数组
144
+ * @param options - 可选配置
145
+ * @returns 选择的字段定义对象
146
+ */
147
+ export function pickTableFields(
148
+ tableName: string,
149
+ fieldNames: string[],
150
+ options?: {
151
+ source?: 'core' | 'project' | 'addon';
152
+ addonName?: string;
153
+ }
154
+ ): Record<string, string> {
155
+ const allFields = loadTableFields(tableName, options);
156
+ const pickedFields: Record<string, string> = {};
157
+
158
+ for (const fieldName of fieldNames) {
159
+ if (fieldName in allFields) {
160
+ pickedFields[fieldName] = allFields[fieldName];
161
+ } else {
162
+ throw new Error(`表 ${tableName} 中不存在字段: ${fieldName}`);
163
+ }
164
+ }
165
+
166
+ return pickedFields;
167
+ }