befly 3.76.0 → 3.76.2

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 (155) hide show
  1. package/Befly.js +159 -0
  2. package/README.md +50 -11
  3. package/apis/admin/delete.js +1 -3
  4. package/apis/admin/insert.js +2 -1
  5. package/apis/admin/select.js +2 -7
  6. package/apis/admin/update.js +3 -8
  7. package/apis/api/select.js +2 -7
  8. package/apis/auth/login.js +16 -20
  9. package/apis/dict/delete.js +1 -1
  10. package/apis/dict/detail.js +1 -3
  11. package/apis/dict/select.js +1 -2
  12. package/apis/dict/update.js +1 -1
  13. package/apis/dictType/delete.js +1 -1
  14. package/apis/dictType/detail.js +1 -1
  15. package/apis/dictType/insert.js +1 -1
  16. package/apis/dictType/select.js +2 -7
  17. package/apis/dictType/update.js +2 -2
  18. package/apis/email/config.js +4 -7
  19. package/apis/email/logList.js +2 -7
  20. package/apis/email/send.js +1 -2
  21. package/apis/email/verify.js +1 -3
  22. package/apis/loginLog/select.js +2 -7
  23. package/apis/menu/select.js +2 -7
  24. package/apis/operateLog/select.js +2 -7
  25. package/apis/role/apiSave.js +2 -2
  26. package/apis/role/menuSave.js +2 -2
  27. package/apis/role/select.js +2 -7
  28. package/apis/source/imageList.js +2 -5
  29. package/apis/tongJi/dailyReport.js +12 -13
  30. package/apis/tongJi/dailyReportSelect.js +1 -2
  31. package/apis/tongJi/dailyStats.js +1 -1
  32. package/apis/tongJi/dailyStatsDistribution.js +2 -2
  33. package/apis/tongJi/errorList.js +1 -2
  34. package/apis/tongJi/errorReport.js +1 -2
  35. package/apis/tongJi/projectDelete.js +1 -3
  36. package/apis/tongJi/projectSelect.js +2 -4
  37. package/apis/tongJi/projectUpdate.js +2 -2
  38. package/apis/upload/file.js +7 -5
  39. package/checks/api.js +43 -66
  40. package/checks/config.js +17 -12
  41. package/checks/corns.js +28 -46
  42. package/checks/field.js +18 -0
  43. package/checks/items.js +16 -60
  44. package/checks/menu.js +28 -39
  45. package/checks/table.js +106 -26
  46. package/checks/validation.js +13 -0
  47. package/configs/beflyConfig.json +6 -6
  48. package/configs/constConfig.js +2 -63
  49. package/exports.js +2 -0
  50. package/hooks/auth.js +11 -7
  51. package/hooks/parser.js +55 -59
  52. package/hooks/permission.js +5 -2
  53. package/hooks/rateLimit.js +25 -62
  54. package/hooks/validator.js +43 -31
  55. package/index.js +43 -344
  56. package/{lib → libs}/cacheHelper.js +3 -8
  57. package/libs/cron.js +60 -0
  58. package/libs/logger/index.js +1 -0
  59. package/libs/logger/logger.js +245 -0
  60. package/libs/logger/sanitize.js +115 -0
  61. package/{lib → libs/mysql}/dbHelper.js +100 -73
  62. package/{lib → libs/mysql}/dbParse.js +16 -9
  63. package/{lib → libs/mysql}/dbUtil.js +15 -32
  64. package/libs/mysql/error.js +29 -0
  65. package/libs/mysql/index.js +6 -0
  66. package/libs/mysql/is.js +29 -0
  67. package/libs/mysql/mysql.js +43 -0
  68. package/libs/mysql/sql/identifier.js +85 -0
  69. package/libs/mysql/sql/index.js +2 -0
  70. package/{lib → libs/mysql/sql}/sqlBuilder.js +2 -3
  71. package/libs/mysql/util.js +44 -0
  72. package/libs/redis/index.js +2 -0
  73. package/libs/redis/redis.js +204 -0
  74. package/libs/redis/redisError.js +8 -0
  75. package/libs/tableModel.js +72 -0
  76. package/libs/tool.js +33 -0
  77. package/libs/validator/compiler.js +248 -0
  78. package/libs/validator/index.js +10 -0
  79. package/libs/validator/parser.js +173 -0
  80. package/libs/validator/util.js +5 -0
  81. package/package.json +10 -8
  82. package/paths.js +2 -3
  83. package/plugins/cache.js +2 -1
  84. package/plugins/email.js +4 -4
  85. package/plugins/mysql.js +5 -15
  86. package/plugins/redis.js +4 -12
  87. package/router/api.js +14 -10
  88. package/router/root.js +14 -0
  89. package/router/static.js +3 -3
  90. package/schemas/api.json +99 -0
  91. package/schemas/config.json +255 -0
  92. package/schemas/corn.json +39 -0
  93. package/schemas/item.json +43 -0
  94. package/schemas/menu.json +44 -0
  95. package/schemas/table.json +80 -0
  96. package/scripts/syncDb/apply.js +87 -0
  97. package/scripts/syncDb/ddl.js +43 -0
  98. package/scripts/syncDb/index.js +175 -30
  99. package/scripts/syncDb/model.js +60 -0
  100. package/scripts/syncDb/plan.js +160 -0
  101. package/scripts/syncDb/preflight.js +98 -0
  102. package/scripts/syncDb/report.js +177 -162
  103. package/scripts/syncDb/snapshot.js +118 -0
  104. package/sync/syncUtil.js +7 -13
  105. package/tables/admin.json +51 -53
  106. package/tables/api.json +29 -40
  107. package/tables/dailyReport.json +85 -73
  108. package/tables/dict.json +27 -39
  109. package/tables/dictType.json +20 -34
  110. package/tables/emailLog.json +49 -47
  111. package/tables/errorReport.json +86 -60
  112. package/tables/file.json +47 -50
  113. package/tables/loginLog.json +68 -55
  114. package/tables/menu.json +20 -34
  115. package/tables/operateLog.json +53 -47
  116. package/tables/project.json +18 -30
  117. package/tables/role.json +33 -37
  118. package/utils/cors.js +37 -41
  119. package/utils/deepMerge.js +3 -1
  120. package/utils/error.js +1 -56
  121. package/utils/id.js +44 -0
  122. package/utils/is.js +6 -243
  123. package/utils/response.js +12 -1
  124. package/utils/scanFiles.js +13 -4
  125. package/utils/scanSources.js +58 -20
  126. package/utils/util.js +11 -163
  127. package/apis/_apis.js +0 -20
  128. package/apis/auth/sendSmsCode.js +0 -24
  129. package/apis/role/delete.js +0 -41
  130. package/apis/role/insert.js +0 -45
  131. package/apis/role/save.js +0 -36
  132. package/apis/role/update.js +0 -66
  133. package/configs/regexpAlias.json +0 -55
  134. package/lib/connect.js +0 -119
  135. package/lib/logger.js +0 -430
  136. package/lib/redisHelper.js +0 -399
  137. package/lib/validator.js +0 -274
  138. package/lib/xmlParse.js +0 -149
  139. package/plugins/config.js +0 -13
  140. package/plugins/cron.js +0 -115
  141. package/plugins/logger.js +0 -17
  142. package/plugins/tool.js +0 -131
  143. package/plugins/xmlParse.js +0 -10
  144. package/schemas/config.js +0 -67
  145. package/scripts/syncDb/context.js +0 -120
  146. package/scripts/syncDb/diff.js +0 -279
  147. package/scripts/syncDb/transform.js +0 -113
  148. package/sql/befly.sql +0 -271
  149. package/utils/email.js +0 -3
  150. package/utils/fieldClear.js +0 -22
  151. package/utils/formatValidationIssues.js +0 -28
  152. package/utils/getClientIp.js +0 -47
  153. package/utils/loggerUtils.js +0 -196
  154. package/utils/regexpUtil.js +0 -52
  155. /package/{lib → libs}/smtpText.js +0 -0
@@ -0,0 +1,43 @@
1
+ import { SQL } from "bun";
2
+
3
+ import { Logger } from "../logger/index.js";
4
+ import { DbHelper } from "./dbHelper.js";
5
+ import { MysqlError } from "./error.js";
6
+
7
+ /**
8
+ * 创建并验证 Bun.SQL MySQL 客户端,返回保持 DbHelper API 的运行时实例。
9
+ */
10
+ export async function createMysql(options) {
11
+ const sql = new SQL({
12
+ adapter: "mysql",
13
+ hostname: options.hostname,
14
+ port: options.port,
15
+ database: options.database,
16
+ username: options.username,
17
+ password: options.password,
18
+ max: options.max,
19
+ bigint: true,
20
+ idleTimeout: options.idleTimeout,
21
+ maxLifetime: options.maxLifetime,
22
+ connectionTimeout: options.connectionTimeout,
23
+ onclose: (client, error) => {
24
+ if (error) Logger.warn(`MySQL 连接关闭:${error.message ?? error}`);
25
+ }
26
+ });
27
+
28
+ try {
29
+ await sql`SELECT 1`;
30
+ } catch (error) {
31
+ await sql.close();
32
+ throw new MysqlError(`Mysql 连接失败 (${options.hostname})`, { cause: error, operation: "connect" });
33
+ }
34
+
35
+ return new DbHelper({
36
+ dbName: options.database,
37
+ sql: sql,
38
+ close: () => sql.close(),
39
+ beflyMode: options.beflyMode,
40
+ queryTimeout: options.queryTimeout,
41
+ tables: options.tables
42
+ });
43
+ }
@@ -0,0 +1,85 @@
1
+ function isString(value) {
2
+ return typeof value === "string";
3
+ }
4
+
5
+ function isQuotedIdentPaired(value) {
6
+ const trimmed = value.trim();
7
+ if (trimmed.length < 2) return false;
8
+
9
+ const first = trimmed[0];
10
+ const last = trimmed.at(-1);
11
+ return (first === "`" && last === "`") || (first === '"' && last === '"');
12
+ }
13
+
14
+ function startsWithIdentifierQuote(value) {
15
+ const trimmed = value.trim();
16
+ return trimmed.startsWith("`") || trimmed.startsWith('"');
17
+ }
18
+
19
+ /**
20
+ * 解析 SQL 标识符引用函数,默认使用 MySQL 反引号。
21
+ */
22
+ export function resolveQuoteIdent(options) {
23
+ if (options?.quoteIdent) return options.quoteIdent;
24
+
25
+ return (identifier) => {
26
+ const trimmed = isString(identifier) ? identifier.trim() : String(identifier).trim();
27
+ return `\`${trimmed.replace(/`/g, "``")}\``;
28
+ };
29
+ }
30
+
31
+ /**
32
+ * 转义字段引用,支持 qualifier.field、* 与单空格别名。
33
+ */
34
+ export function escapeField(field, quoteIdent) {
35
+ if (!isString(field)) return field;
36
+
37
+ const trimmed = field.trim();
38
+ if (!trimmed || trimmed === "*" || isQuotedIdentPaired(trimmed)) return trimmed;
39
+ if (startsWithIdentifierQuote(trimmed) && !isQuotedIdentPaired(trimmed) && !trimmed.includes(".")) return trimmed;
40
+ if (trimmed.includes("(") || trimmed.includes(")") || /\s+AS\s+/i.test(trimmed)) return trimmed;
41
+
42
+ const aliasMatch = trimmed.match(/^([^\s]+)\s+([^\s]+)$/);
43
+ if (aliasMatch) return `${escapeField(aliasMatch[1].trim(), quoteIdent)} ${aliasMatch[2].trim()}`;
44
+
45
+ if (trimmed.includes(".")) {
46
+ return trimmed
47
+ .split(".")
48
+ .map((part) => {
49
+ const cleanPart = part.trim();
50
+ if (!cleanPart || cleanPart === "*" || isQuotedIdentPaired(cleanPart) || startsWithIdentifierQuote(cleanPart)) return cleanPart;
51
+ return quoteIdent(cleanPart);
52
+ })
53
+ .join(".");
54
+ }
55
+
56
+ return quoteIdent(trimmed);
57
+ }
58
+
59
+ /**
60
+ * 转义表引用,支持 schema.table 与单个别名。
61
+ */
62
+ export function escapeTable(table, quoteIdent) {
63
+ if (!isString(table)) return table;
64
+
65
+ const trimmed = table.trim();
66
+ if (!trimmed || isQuotedIdentPaired(trimmed)) return trimmed;
67
+ if (startsWithIdentifierQuote(trimmed) && !isQuotedIdentPaired(trimmed) && !trimmed.includes(".")) return trimmed;
68
+
69
+ const parts = trimmed.split(/\s+/).filter((part) => part.length > 0);
70
+ if (parts.length > 2) return trimmed;
71
+
72
+ const namePart = parts[0].trim();
73
+ if (namePart.split(".").some((part) => startsWithIdentifierQuote(part.trim()) && !isQuotedIdentPaired(part.trim()))) return trimmed;
74
+
75
+ const escapedName = namePart
76
+ .split(".")
77
+ .map((part) => {
78
+ const cleanPart = part.trim();
79
+ if (!cleanPart || isQuotedIdentPaired(cleanPart) || startsWithIdentifierQuote(cleanPart)) return cleanPart;
80
+ return quoteIdent(cleanPart);
81
+ })
82
+ .join(".");
83
+
84
+ return parts.length === 2 ? `${escapedName} ${parts[1].trim()}` : escapedName;
85
+ }
@@ -0,0 +1,2 @@
1
+ export { escapeField, escapeTable, resolveQuoteIdent } from "./identifier.js";
2
+ export { SqlBuilder } from "./sqlBuilder.js";
@@ -24,8 +24,7 @@
24
24
  * - 本类不校验节点合法性(信任上层),仅负责编译
25
25
  */
26
26
 
27
- import { isNonEmptyString } from "../utils/is.js";
28
- import { escapeField, escapeTable, resolveQuoteIdent } from "./dbUtil.js";
27
+ import { escapeField, escapeTable, resolveQuoteIdent } from "./identifier.js";
29
28
 
30
29
  /**
31
30
  * SQL 构建器类
@@ -429,7 +428,7 @@ export class SqlBuilder {
429
428
  setSqlList.push(`${quotedField} = CASE ${quotedId} ${whenList.join(" ")} ELSE ${quotedField} END`);
430
429
  }
431
430
 
432
- if (isNonEmptyString(options.updatedAtField)) {
431
+ if (typeof options.updatedAtField === "string" && options.updatedAtField.trim().length > 0) {
433
432
  setSqlList.push(`${options.quoteIdent(options.updatedAtField)} = ?`);
434
433
  args.push(options.updatedAtValue);
435
434
  }
@@ -0,0 +1,44 @@
1
+ import { isPlainObject } from "./is.js";
2
+
3
+ function toWordParts(input) {
4
+ const normalized = String(input)
5
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
6
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
7
+ .replace(/[^a-zA-Z0-9]+/g, " ")
8
+ .trim();
9
+ return normalized ? normalized.split(/\s+/) : [];
10
+ }
11
+
12
+ export function camelCase(input) {
13
+ const parts = toWordParts(input);
14
+ if (parts.length === 0) return "";
15
+ return (
16
+ parts[0].toLowerCase() +
17
+ parts
18
+ .slice(1)
19
+ .map((part) => part[0].toUpperCase() + part.slice(1).toLowerCase())
20
+ .join("")
21
+ );
22
+ }
23
+
24
+ export function snakeCase(input) {
25
+ return toWordParts(input)
26
+ .map((part) => part.toLowerCase())
27
+ .join("_");
28
+ }
29
+
30
+ export function keysToSnake(obj) {
31
+ if (!isPlainObject(obj)) return obj;
32
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [snakeCase(key), value]));
33
+ }
34
+
35
+ export function canConvertToNumber(value) {
36
+ if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < BigInt(Number.MIN_SAFE_INTEGER)) return null;
37
+ const number = Number(value);
38
+ return Number.isSafeInteger(number) ? number : null;
39
+ }
40
+
41
+ export function fieldClear(data, options = {}) {
42
+ if (!isPlainObject(data)) return data;
43
+ return Object.fromEntries(Object.entries(data).filter(([, value]) => !options.excludeValues?.includes(value)));
44
+ }
@@ -0,0 +1,2 @@
1
+ export { createRedis, RedisHelper } from "./redis.js";
2
+ export { RedisError } from "./redisError.js";
@@ -0,0 +1,204 @@
1
+ import { RedisClient } from "bun";
2
+
3
+ import { Logger } from "../logger/index.js";
4
+ import { RedisError } from "./redisError.js";
5
+
6
+ function buildRedisUrl(config) {
7
+ if (config.username) {
8
+ return `redis://${encodeURIComponent(config.username)}:${encodeURIComponent(config.password || "")}@${config.hostname}:${config.port}/${config.db}`;
9
+ }
10
+ if (config.password) {
11
+ return `redis://:${encodeURIComponent(config.password)}@${config.hostname}:${config.port}/${config.db}`;
12
+ }
13
+ return `redis://${config.hostname}:${config.port}/${config.db}`;
14
+ }
15
+
16
+ /**
17
+ * Bun RedisClient 高层操作封装。除 ping 外,操作失败记录日志并返回约定 fallback。
18
+ */
19
+ export class RedisHelper {
20
+ constructor(options) {
21
+ this.client = options.client;
22
+ this.prefix = options.prefix ? `${options.prefix}:` : "";
23
+ }
24
+
25
+ async safeCall(label, operation, fallback) {
26
+ try {
27
+ return await operation();
28
+ } catch (error) {
29
+ Logger.error(`Redis ${label} 错误`, error);
30
+ return fallback;
31
+ }
32
+ }
33
+
34
+ async setObject(key, obj, ttl = null) {
35
+ return this.safeCall(
36
+ "setObject",
37
+ async () => {
38
+ const data = JSON.stringify(obj);
39
+ const pkey = `${this.prefix}${key}`;
40
+ return ttl ? this.client.setex(pkey, ttl, data) : this.client.set(pkey, data);
41
+ },
42
+ null
43
+ );
44
+ }
45
+
46
+ async getObject(key) {
47
+ return this.safeCall(
48
+ "getObject",
49
+ async () => {
50
+ const data = await this.client.get(`${this.prefix}${key}`);
51
+ return data ? JSON.parse(data) : null;
52
+ },
53
+ null
54
+ );
55
+ }
56
+
57
+ async delObject(key) {
58
+ return this.safeCall("delObject", async () => {
59
+ await this.client.del(`${this.prefix}${key}`);
60
+ });
61
+ }
62
+
63
+ async setString(key, value, ttl = null) {
64
+ return this.safeCall(
65
+ "setString",
66
+ async () => {
67
+ const pkey = `${this.prefix}${key}`;
68
+ return ttl ? this.client.setex(pkey, ttl, value) : this.client.set(pkey, value);
69
+ },
70
+ null
71
+ );
72
+ }
73
+
74
+ async getString(key) {
75
+ return this.safeCall("getString", () => this.client.get(`${this.prefix}${key}`), null);
76
+ }
77
+
78
+ async exists(key) {
79
+ return this.safeCall("exists", () => this.client.exists(`${this.prefix}${key}`), false);
80
+ }
81
+
82
+ async incr(key) {
83
+ return this.safeCall("incr", () => this.client.incr(`${this.prefix}${key}`), 0);
84
+ }
85
+
86
+ async incrWithExpire(key, seconds) {
87
+ return this.safeCall(
88
+ "incrWithExpire",
89
+ async () => {
90
+ const pkey = `${this.prefix}${key}`;
91
+ const result = await this.client.incr(pkey);
92
+ if (result === 1) await this.client.expire(pkey, seconds);
93
+ return result;
94
+ },
95
+ 0
96
+ );
97
+ }
98
+
99
+ async expire(key, seconds) {
100
+ return this.safeCall("expire", () => this.client.expire(`${this.prefix}${key}`, seconds), 0);
101
+ }
102
+
103
+ async ttl(key) {
104
+ return this.safeCall("ttl", () => this.client.ttl(`${this.prefix}${key}`), -1);
105
+ }
106
+
107
+ async sadd(key, members) {
108
+ return this.safeCall(
109
+ "sadd",
110
+ async () => {
111
+ if (members.length === 0) return 0;
112
+ return this.client.sadd(`${this.prefix}${key}`, ...members);
113
+ },
114
+ 0
115
+ );
116
+ }
117
+
118
+ async sismember(key, member) {
119
+ return this.safeCall("sismember", () => this.client.sismember(`${this.prefix}${key}`, member), false);
120
+ }
121
+
122
+ async scard(key) {
123
+ return this.safeCall("scard", () => this.client.scard(`${this.prefix}${key}`), 0);
124
+ }
125
+
126
+ async smembers(key) {
127
+ return this.safeCall("smembers", () => this.client.smembers(`${this.prefix}${key}`), []);
128
+ }
129
+
130
+ async saddBatch(items) {
131
+ return this.safeCall(
132
+ "saddBatch",
133
+ async () => {
134
+ if (items.length === 0) return 0;
135
+ const results = await Promise.all(items.map((item) => this.sadd(item.key, item.members)));
136
+ return results.reduce((sum, count) => sum + count, 0);
137
+ },
138
+ 0
139
+ );
140
+ }
141
+
142
+ async del(key) {
143
+ return this.safeCall("del", () => this.client.del(`${this.prefix}${key}`), 0);
144
+ }
145
+
146
+ async delBatch(keys) {
147
+ return this.safeCall(
148
+ "delBatch",
149
+ async () => {
150
+ if (keys.length === 0) return 0;
151
+ const results = await Promise.all(keys.map((key) => this.client.del(`${this.prefix}${key}`)));
152
+ return results.reduce((sum, count) => sum + count, 0);
153
+ },
154
+ 0
155
+ );
156
+ }
157
+
158
+ async info(section) {
159
+ return this.safeCall(
160
+ "info",
161
+ async () => {
162
+ if (typeof this.client.info === "function") return this.client.info(section);
163
+ if (typeof this.client.send === "function") return this.client.send("INFO", section ? [String(section)] : []);
164
+ throw new RedisError("Redis info 不支持", { operation: "info" });
165
+ },
166
+ ""
167
+ );
168
+ }
169
+
170
+ async ping() {
171
+ try {
172
+ return await this.client.ping();
173
+ } catch (error) {
174
+ Logger.error("Redis ping 错误", error);
175
+ throw new RedisError("Redis ping 错误", { cause: error, operation: "ping" });
176
+ }
177
+ }
178
+
179
+ async close() {
180
+ await this.client.close();
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 创建并验证 Bun RedisClient,返回带前缀和生命周期能力的 RedisHelper。
186
+ */
187
+ export async function createRedis(options) {
188
+ const client = new RedisClient(buildRedisUrl(options));
189
+ client.onclose = (error) => {
190
+ Logger.warn(error ? `Redis 断开连接:${error.message ?? error}` : "Redis 断开连接");
191
+ };
192
+
193
+ try {
194
+ await client.ping();
195
+ } catch (error) {
196
+ await client.close();
197
+ throw new RedisError(`Redis 连接失败 (${options.hostname})`, { cause: error, operation: "connect" });
198
+ }
199
+
200
+ return new RedisHelper({
201
+ client: client,
202
+ prefix: options.prefix
203
+ });
204
+ }
@@ -0,0 +1,8 @@
1
+ export class RedisError extends Error {
2
+ constructor(message, options = {}) {
3
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
4
+ this.name = "RedisError";
5
+ this.code = options.code || "runtime";
6
+ this.operation = options.operation;
7
+ }
8
+ }
@@ -0,0 +1,72 @@
1
+ import { TABLE_AUTO_FIELD_NAMES } from "../configs/constConfig.js";
2
+ import { isPlainObject, isString } from "../utils/is.js";
3
+ import { snakeCase } from "../utils/util.js";
4
+
5
+ const tableAutoFieldNameSet = new Set(TABLE_AUTO_FIELD_NAMES);
6
+
7
+ const automaticFields = {
8
+ id: { name: "主键 ID", paramType: "integer", fieldType: "integer", fieldNullable: false, primary: true, autoIncrement: false, automatic: true },
9
+ state: { name: "状态", paramType: "integer", fieldType: "integer", fieldNullable: false, fieldDefault: 1, primary: false, autoIncrement: false, automatic: true },
10
+ createdAt: { name: "创建时间", paramType: "integer", fieldType: "integer", fieldNullable: false, fieldDefault: 0, primary: false, autoIncrement: false, automatic: true },
11
+ updatedAt: { name: "更新时间", paramType: "integer", fieldType: "integer", fieldNullable: false, fieldDefault: 0, primary: false, autoIncrement: false, automatic: true },
12
+ deletedAt: { name: "删除时间", paramType: "integer", fieldType: "integer", fieldNullable: true, primary: false, autoIncrement: false, automatic: true }
13
+ };
14
+
15
+ function parseIndexes(indexDefinitions) {
16
+ if (indexDefinitions === undefined) return {};
17
+ if (!isPlainObject(indexDefinitions)) return indexDefinitions;
18
+ const indexes = {};
19
+ for (const [indexName, definition] of Object.entries(indexDefinitions)) {
20
+ if (!isString(definition)) {
21
+ indexes[indexName] = definition;
22
+ continue;
23
+ }
24
+ const [type, fieldsText] = definition.split("#");
25
+ indexes[indexName] = {
26
+ type: type,
27
+ fields: isString(fieldsText) ? fieldsText.split(",") : []
28
+ };
29
+ }
30
+ return indexes;
31
+ }
32
+
33
+ /**
34
+ * 将单个 tables JSON 转换为运行时、检查与数据库同步共用的唯一标准模型。
35
+ */
36
+ export function buildTableModel(tableDefinition, options) {
37
+ if (options.source === "app" && options.fileName.toLowerCase().startsWith("befly")) throw new TypeError(`App Table 禁止使用 befly 保留前缀:${options.fileName}`);
38
+ const auto = options.source === "core" || options.beflyMode !== "manual";
39
+ const primary = auto ? "id" : tableDefinition?.$tablePrimary;
40
+ const businessFields = {};
41
+ if (isPlainObject(tableDefinition)) {
42
+ for (const [key, value] of Object.entries(tableDefinition)) {
43
+ if (!key.startsWith("$")) {
44
+ businessFields[key] = isPlainObject(value)
45
+ ? {
46
+ fieldNullable: false,
47
+ ...value,
48
+ primary: key === primary,
49
+ autoIncrement: !auto && key === primary,
50
+ automatic: false
51
+ }
52
+ : value;
53
+ }
54
+ }
55
+ }
56
+
57
+ const codeName = options.source === "core" ? `befly${options.fileName.charAt(0).toUpperCase()}${options.fileName.slice(1)}` : options.fileName;
58
+ return {
59
+ name: tableDefinition?.$tableName,
60
+ source: options.source,
61
+ fileName: options.fileName,
62
+ codeName: codeName,
63
+ dbName: snakeCase(codeName),
64
+ primary: primary,
65
+ indexes: parseIndexes(tableDefinition?.$tableIndexes),
66
+ fields: auto ? { ...automaticFields, ...businessFields } : businessFields
67
+ };
68
+ }
69
+
70
+ export function isTableAutoFieldName(fieldName) {
71
+ return tableAutoFieldNameSet.has(fieldName);
72
+ }
package/libs/tool.js ADDED
@@ -0,0 +1,33 @@
1
+ import { genId } from "../utils/id.js";
2
+ import { isNumber, isString } from "../utils/is.js";
3
+
4
+ export function Yes(msg, data = {}, other = {}) {
5
+ return { code: 0, msg: msg, data: data, ...other };
6
+ }
7
+
8
+ export function No(msg, data = null, other = {}) {
9
+ return { code: 1, msg: msg, data: data, ...other };
10
+ }
11
+
12
+ export function Fail(msg, data = null, other = {}) {
13
+ const error = new Error(msg);
14
+ error.isBeflyResponse = true;
15
+ error.payload = No(msg, data, other);
16
+ throw error;
17
+ }
18
+
19
+ export function Raw(ctx, data, options = {}) {
20
+ const contentType = options.contentType || (isString(data) ? (data.trim().startsWith("<") ? "application/xml" : "text/plain") : "application/json");
21
+ const body = isString(data) ? data : JSON.stringify(data);
22
+
23
+ return new Response(body, {
24
+ status: isNumber(options.status) ? options.status : 200,
25
+ headers: {
26
+ ...ctx.corsHeaders,
27
+ "Content-Type": contentType,
28
+ ...options.headers
29
+ }
30
+ });
31
+ }
32
+
33
+ export const tool = { Yes: Yes, No: No, Fail: Fail, Raw: Raw, genId: genId };