befly 3.17.0 → 3.17.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 (150) hide show
  1. package/README.md +6 -0
  2. package/apis/admin/cacheRefresh.js +122 -0
  3. package/apis/admin/del.js +34 -0
  4. package/apis/admin/detail.js +23 -0
  5. package/apis/admin/ins.js +69 -0
  6. package/apis/admin/list.js +28 -0
  7. package/apis/admin/upd.js +95 -0
  8. package/apis/api/all.js +24 -0
  9. package/apis/api/list.js +31 -0
  10. package/apis/auth/login.js +123 -0
  11. package/apis/auth/sendSmsCode.js +24 -0
  12. package/apis/dashboard/configStatus.js +39 -0
  13. package/apis/dashboard/environmentInfo.js +43 -0
  14. package/apis/dashboard/performanceMetrics.js +20 -0
  15. package/apis/dashboard/permissionStats.js +27 -0
  16. package/apis/dashboard/serviceStatus.js +75 -0
  17. package/apis/dashboard/systemInfo.js +19 -0
  18. package/apis/dashboard/systemOverview.js +30 -0
  19. package/apis/dashboard/systemResources.js +106 -0
  20. package/apis/dict/all.js +23 -0
  21. package/apis/dict/del.js +16 -0
  22. package/apis/dict/detail.js +27 -0
  23. package/apis/dict/ins.js +51 -0
  24. package/apis/dict/items.js +30 -0
  25. package/apis/dict/list.js +36 -0
  26. package/apis/dict/upd.js +74 -0
  27. package/apis/dictType/all.js +16 -0
  28. package/apis/dictType/del.js +38 -0
  29. package/apis/dictType/detail.js +20 -0
  30. package/apis/dictType/ins.js +37 -0
  31. package/apis/dictType/list.js +26 -0
  32. package/apis/dictType/upd.js +51 -0
  33. package/apis/email/config.js +25 -0
  34. package/apis/email/logList.js +23 -0
  35. package/apis/email/send.js +66 -0
  36. package/apis/email/verify.js +21 -0
  37. package/apis/loginLog/list.js +23 -0
  38. package/apis/menu/all.js +41 -0
  39. package/apis/menu/list.js +25 -0
  40. package/apis/operateLog/list.js +23 -0
  41. package/apis/role/all.js +21 -0
  42. package/apis/role/apiSave.js +43 -0
  43. package/apis/role/apis.js +22 -0
  44. package/apis/role/del.js +49 -0
  45. package/apis/role/detail.js +32 -0
  46. package/apis/role/ins.js +46 -0
  47. package/apis/role/list.js +27 -0
  48. package/apis/role/menuSave.js +42 -0
  49. package/apis/role/menus.js +22 -0
  50. package/apis/role/save.js +40 -0
  51. package/apis/role/upd.js +50 -0
  52. package/apis/sysConfig/all.js +16 -0
  53. package/apis/sysConfig/del.js +36 -0
  54. package/apis/sysConfig/get.js +49 -0
  55. package/apis/sysConfig/ins.js +50 -0
  56. package/apis/sysConfig/list.js +24 -0
  57. package/apis/sysConfig/upd.js +62 -0
  58. package/checks/api.js +55 -0
  59. package/checks/config.js +107 -0
  60. package/checks/hook.js +38 -0
  61. package/checks/menu.js +58 -0
  62. package/checks/plugin.js +38 -0
  63. package/checks/table.js +78 -0
  64. package/configs/beflyConfig.json +61 -0
  65. package/configs/beflyMenus.json +85 -0
  66. package/configs/constConfig.js +34 -0
  67. package/configs/regexpAlias.json +55 -0
  68. package/hooks/auth.js +34 -0
  69. package/hooks/cors.js +39 -0
  70. package/hooks/parser.js +90 -0
  71. package/hooks/permission.js +71 -0
  72. package/hooks/validator.js +43 -0
  73. package/index.js +326 -0
  74. package/lib/cacheHelper.js +483 -0
  75. package/lib/cacheKeys.js +42 -0
  76. package/lib/connect.js +120 -0
  77. package/lib/dbHelper/builders.js +698 -0
  78. package/lib/dbHelper/context.js +131 -0
  79. package/lib/dbHelper/dataOps.js +505 -0
  80. package/lib/dbHelper/execute.js +65 -0
  81. package/lib/dbHelper/index.js +27 -0
  82. package/lib/dbHelper/transaction.js +43 -0
  83. package/lib/dbHelper/util.js +58 -0
  84. package/lib/dbHelper/validate.js +549 -0
  85. package/lib/emailHelper.js +110 -0
  86. package/lib/logger.js +604 -0
  87. package/lib/redisHelper.js +684 -0
  88. package/lib/sqlBuilder/batch.js +113 -0
  89. package/lib/sqlBuilder/check.js +150 -0
  90. package/lib/sqlBuilder/compiler.js +347 -0
  91. package/lib/sqlBuilder/errors.js +60 -0
  92. package/lib/sqlBuilder/index.js +218 -0
  93. package/lib/sqlBuilder/parser.js +296 -0
  94. package/lib/sqlBuilder/util.js +260 -0
  95. package/lib/validator.js +303 -0
  96. package/package.json +19 -12
  97. package/paths.js +112 -0
  98. package/plugins/cache.js +16 -0
  99. package/plugins/config.js +11 -0
  100. package/plugins/email.js +27 -0
  101. package/plugins/logger.js +20 -0
  102. package/plugins/mysql.js +36 -0
  103. package/plugins/redis.js +34 -0
  104. package/plugins/tool.js +155 -0
  105. package/router/api.js +140 -0
  106. package/router/static.js +71 -0
  107. package/sql/admin.sql +18 -0
  108. package/sql/api.sql +12 -0
  109. package/sql/dict.sql +13 -0
  110. package/sql/dictType.sql +12 -0
  111. package/sql/emailLog.sql +20 -0
  112. package/sql/loginLog.sql +25 -0
  113. package/sql/menu.sql +12 -0
  114. package/sql/operateLog.sql +22 -0
  115. package/sql/role.sql +14 -0
  116. package/sql/sysConfig.sql +16 -0
  117. package/sync/api.js +93 -0
  118. package/sync/cache.js +13 -0
  119. package/sync/dev.js +171 -0
  120. package/sync/menu.js +99 -0
  121. package/tables/admin.json +56 -0
  122. package/tables/api.json +26 -0
  123. package/tables/dict.json +30 -0
  124. package/tables/dictType.json +24 -0
  125. package/tables/emailLog.json +61 -0
  126. package/tables/loginLog.json +86 -0
  127. package/tables/menu.json +24 -0
  128. package/tables/operateLog.json +68 -0
  129. package/tables/role.json +32 -0
  130. package/tables/sysConfig.json +43 -0
  131. package/utils/calcPerfTime.js +13 -0
  132. package/utils/cors.js +17 -0
  133. package/utils/deepMerge.js +78 -0
  134. package/utils/fieldClear.js +65 -0
  135. package/utils/formatYmdHms.js +23 -0
  136. package/utils/formatZodIssues.js +109 -0
  137. package/utils/getClientIp.js +47 -0
  138. package/utils/importDefault.js +51 -0
  139. package/utils/is.js +462 -0
  140. package/utils/loggerUtils.js +185 -0
  141. package/utils/processInfo.js +39 -0
  142. package/utils/regexpUtil.js +52 -0
  143. package/utils/response.js +114 -0
  144. package/utils/scanFiles.js +124 -0
  145. package/utils/scanSources.js +68 -0
  146. package/utils/sortModules.js +75 -0
  147. package/utils/toSessionTtlSeconds.js +14 -0
  148. package/utils/util.js +374 -0
  149. package/befly.js +0 -16413
  150. package/befly.min.js +0 -72
@@ -0,0 +1,27 @@
1
+ import { isNonEmptyString } from "../../utils/is.js";
2
+ import { builderMethods } from "./builders.js";
3
+ import { dataOpsMethods } from "./dataOps.js";
4
+ import { executeMethods } from "./execute.js";
5
+ import { transactionMethods } from "./transaction.js";
6
+ import { convertBigIntFields } from "./context.js";
7
+
8
+ function DbHelper(options) {
9
+ this.redis = options.redis;
10
+
11
+ if (!isNonEmptyString(options.dbName)) {
12
+ throw new Error("DbHelper 初始化失败:dbName 必须为非空字符串", {
13
+ cause: null,
14
+ code: "validation"
15
+ });
16
+ }
17
+ this.dbName = options.dbName;
18
+
19
+ this.sql = options.sql || null;
20
+ this.isTransaction = Boolean(options.sql);
21
+ this.idMode = options.idMode === "autoId" ? "autoId" : "timeId";
22
+ }
23
+
24
+ Object.assign(DbHelper.prototype, builderMethods, executeMethods, dataOpsMethods, transactionMethods);
25
+ DbHelper.convertBigIntFields = convertBigIntFields;
26
+
27
+ export { DbHelper };
@@ -0,0 +1,43 @@
1
+ import { hasBegin, isBeflyResponse, TransAbortError } from "./context.js";
2
+
3
+ export const transactionMethods = {
4
+ async trans(callback) {
5
+ if (this.isTransaction) {
6
+ const innerResult = await callback(this);
7
+ if (isBeflyResponse(innerResult) && innerResult.code !== 0) {
8
+ throw new TransAbortError(innerResult);
9
+ }
10
+ return innerResult;
11
+ }
12
+
13
+ const sql = this.sql;
14
+ if (!sql) {
15
+ throw new Error("数据库连接未初始化", {
16
+ cause: null,
17
+ code: "runtime"
18
+ });
19
+ }
20
+ if (!hasBegin(sql)) {
21
+ throw new Error("当前 SQL 客户端不支持事务 begin() 方法", {
22
+ cause: null,
23
+ code: "runtime"
24
+ });
25
+ }
26
+
27
+ try {
28
+ return await sql.begin(async (tx) => {
29
+ const trans = new this.constructor({ redis: this.redis, dbName: this.dbName, sql: tx, idMode: this.idMode });
30
+ const result = await callback(trans);
31
+ if (isBeflyResponse(result) && result.code !== 0) {
32
+ throw new TransAbortError(result);
33
+ }
34
+ return result;
35
+ });
36
+ } catch (error) {
37
+ if (error instanceof TransAbortError) {
38
+ return error.payload;
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ };
@@ -0,0 +1,58 @@
1
+ import { isNullable } from "../../utils/is.js";
2
+ import { snakeCase } from "../../utils/util.js";
3
+ import { parseTableRef } from "./validate.js";
4
+
5
+ export function normalizeTableRef(tableRef) {
6
+ const parsed = parseTableRef(tableRef);
7
+ const schemaPart = parsed.schema ? snakeCase(parsed.schema) : null;
8
+ const tablePart = snakeCase(parsed.table);
9
+ const aliasPart = parsed.alias ? snakeCase(parsed.alias) : null;
10
+ let result = schemaPart ? `${schemaPart}.${tablePart}` : tablePart;
11
+ if (aliasPart) {
12
+ result = `${result} ${aliasPart}`;
13
+ }
14
+ return result;
15
+ }
16
+
17
+ export function getJoinMainQualifier(tableRef) {
18
+ const parsed = parseTableRef(tableRef);
19
+ if (parsed.alias) {
20
+ return snakeCase(parsed.alias);
21
+ }
22
+ return snakeCase(parsed.table);
23
+ }
24
+
25
+ export function toNumberFromSql(value) {
26
+ if (isNullable(value)) {
27
+ return 0;
28
+ }
29
+
30
+ if (typeof value === "number") {
31
+ if (!Number.isFinite(value)) {
32
+ return 0;
33
+ }
34
+ return value;
35
+ }
36
+
37
+ if (typeof value === "bigint") {
38
+ const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
39
+ const minSafe = BigInt(Number.MIN_SAFE_INTEGER);
40
+ if (value <= maxSafe && value >= minSafe) {
41
+ return Number(value);
42
+ }
43
+ return 0;
44
+ }
45
+
46
+ if (typeof value === "string") {
47
+ if (!value.trim()) {
48
+ return 0;
49
+ }
50
+ const parsed = Number(value);
51
+ if (!Number.isFinite(parsed)) {
52
+ return 0;
53
+ }
54
+ return parsed;
55
+ }
56
+
57
+ return 0;
58
+ }
@@ -0,0 +1,549 @@
1
+ import { isFiniteNumber, isNonEmptyString, isNullable, isPlainObject, isString } from "../../utils/is.js";
2
+ import { snakeCase } from "../../utils/util.js";
3
+
4
+ function assertNonEmptyString(value, label) {
5
+ if (!isNonEmptyString(value)) {
6
+ throw new Error(`${label} 必须是非空字符串`, {
7
+ cause: null,
8
+ code: "validation"
9
+ });
10
+ }
11
+ }
12
+
13
+ function assertPlainObjectValue(value, label) {
14
+ if (!isPlainObject(value)) {
15
+ throw new Error(`${label} 必须是对象`, {
16
+ cause: null,
17
+ code: "validation"
18
+ });
19
+ }
20
+ }
21
+
22
+ export function validateWhereObject(where, label, required = false) {
23
+ if (isNullable(where)) {
24
+ if (required) {
25
+ throw new Error(`${label} 必须是对象`, {
26
+ cause: null,
27
+ code: "validation"
28
+ });
29
+ }
30
+ return;
31
+ }
32
+
33
+ if (!isPlainObject(where)) {
34
+ throw new Error(`${label} 必须是对象`, {
35
+ cause: null,
36
+ code: "validation"
37
+ });
38
+ }
39
+ }
40
+
41
+ function validateOrderByItems(orderBy, label) {
42
+ if (!Array.isArray(orderBy)) {
43
+ throw new Error(`${label} 必须是数组`, {
44
+ cause: null,
45
+ code: "validation"
46
+ });
47
+ }
48
+
49
+ for (const item of orderBy) {
50
+ if (!isString(item) || !item.includes("#")) {
51
+ throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
52
+ cause: null,
53
+ code: "validation"
54
+ });
55
+ }
56
+
57
+ const parts = item.split("#");
58
+ if (parts.length !== 2) {
59
+ throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
60
+ cause: null,
61
+ code: "validation"
62
+ });
63
+ }
64
+
65
+ const field = isString(parts[0]) ? parts[0].trim() : "";
66
+ const direction = isString(parts[1]) ? parts[1].trim().toUpperCase() : "";
67
+ if (!field) {
68
+ throw new Error(`${label} 中字段名不能为空`, {
69
+ cause: null,
70
+ code: "validation"
71
+ });
72
+ }
73
+ if (direction !== "ASC" && direction !== "DESC") {
74
+ throw new Error(`${label} 方向必须是 ASC 或 DESC`, {
75
+ cause: null,
76
+ code: "validation"
77
+ });
78
+ }
79
+ }
80
+ }
81
+
82
+ function validateJoins(joins, label) {
83
+ if (!Array.isArray(joins)) {
84
+ throw new Error(`${label} 必须是数组`, {
85
+ cause: null,
86
+ code: "validation"
87
+ });
88
+ }
89
+
90
+ for (let i = 0; i < joins.length; i++) {
91
+ const join = joins[i];
92
+ if (!isPlainObject(join)) {
93
+ throw new Error(`${label}[${i}] 必须是对象`, {
94
+ cause: null,
95
+ code: "validation"
96
+ });
97
+ }
98
+
99
+ assertNonEmptyString(join.table, `${label}[${i}].table`);
100
+ assertNonEmptyString(join.on, `${label}[${i}].on`);
101
+
102
+ if (!isNullable(join.type)) {
103
+ const type = isString(join.type) ? join.type.trim().toLowerCase() : "";
104
+ if (type !== "left" && type !== "right" && type !== "inner") {
105
+ throw new Error(`${label}[${i}].type 只允许 left/right/inner`, {
106
+ cause: null,
107
+ code: "validation"
108
+ });
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ export function validateQueryOptions(options, label) {
115
+ if (!isPlainObject(options)) {
116
+ throw new Error(`${label} 必须是对象`, {
117
+ cause: null,
118
+ code: "validation"
119
+ });
120
+ }
121
+
122
+ assertNonEmptyString(options.table, `${label}.table`);
123
+
124
+ if (!isNullable(options.where)) {
125
+ validateWhereObject(options.where, `${label}.where`);
126
+ }
127
+
128
+ if (!isNullable(options.fields) && !Array.isArray(options.fields)) {
129
+ throw new Error(`${label}.fields 必须是数组`, {
130
+ cause: null,
131
+ code: "validation"
132
+ });
133
+ }
134
+
135
+ if (!isNullable(options.orderBy)) {
136
+ validateOrderByItems(options.orderBy, `${label}.orderBy`);
137
+ }
138
+
139
+ if (!isNullable(options.joins)) {
140
+ validateJoins(options.joins, `${label}.joins`);
141
+ }
142
+
143
+ if (!isNullable(options.page) && (!isFiniteNumber(options.page) || Math.floor(options.page) !== options.page)) {
144
+ throw new Error(`${label}.page 必须是整数`, {
145
+ cause: null,
146
+ code: "validation"
147
+ });
148
+ }
149
+
150
+ if (!isNullable(options.limit) && (!isFiniteNumber(options.limit) || Math.floor(options.limit) !== options.limit)) {
151
+ throw new Error(`${label}.limit 必须是整数`, {
152
+ cause: null,
153
+ code: "validation"
154
+ });
155
+ }
156
+ }
157
+
158
+ export function validateWriteData(data, label) {
159
+ assertPlainObjectValue(data, label);
160
+ }
161
+
162
+ export function validateBatchDataList(dataList, label) {
163
+ if (!Array.isArray(dataList)) {
164
+ throw new Error(`${label} 必须是数组`, {
165
+ cause: null,
166
+ code: "validation"
167
+ });
168
+ }
169
+
170
+ for (let i = 0; i < dataList.length; i++) {
171
+ if (!isPlainObject(dataList[i])) {
172
+ throw new Error(`${label}[${i}] 必须是对象`, {
173
+ cause: null,
174
+ code: "validation"
175
+ });
176
+ }
177
+ }
178
+ }
179
+
180
+ export function validateTableName(table, label) {
181
+ assertNonEmptyString(table, label);
182
+ }
183
+
184
+ export function validateTableWhereOptions(options, label, required = false) {
185
+ validateTableName(options.table, `${label}.table`);
186
+ validateWhereObject(options.where, `${label}.where`, required);
187
+ }
188
+
189
+ export function validateTableDataOptions(options, label) {
190
+ validateTableName(options.table, `${label}.table`);
191
+ validateWriteData(options.data, `${label}.data`);
192
+ }
193
+
194
+ export function validateTableBatchDataOptions(table, dataList, label) {
195
+ validateTableName(table, `${label}.table`);
196
+ validateBatchDataList(dataList, `${label}.dataList`);
197
+ }
198
+
199
+ export function validateNoJoinReadOptions(options, label, joinErrorMessage) {
200
+ validateTableWhereOptions(options, label, false);
201
+ validateNoJoins(options.joins, joinErrorMessage);
202
+ validateSimpleTableName(options.table, label);
203
+ }
204
+
205
+ export function validateIncrementOptions(table, field, where, value, label) {
206
+ validateTableWhereOptions({ table: table, where: where }, label, true);
207
+ validateSafeFieldName(snakeCase(table));
208
+ validateSafeFieldName(snakeCase(field));
209
+ validateIncrementValue(table, field, value);
210
+ }
211
+
212
+ export function validateSimpleTableName(rawTable, label) {
213
+ const table = isString(rawTable) ? rawTable.trim() : "";
214
+ if (!table) {
215
+ throw new Error(`${label}.table 不能为空`, {
216
+ cause: null,
217
+ code: "validation"
218
+ });
219
+ }
220
+ if (table.includes(" ")) {
221
+ throw new Error(`${label} 不支持别名表写法(table: ${table})`, {
222
+ cause: null,
223
+ code: "validation"
224
+ });
225
+ }
226
+ if (table.includes(".")) {
227
+ throw new Error(`${label} 不支持 schema.table 写法(table: ${table})`, {
228
+ cause: null,
229
+ code: "validation"
230
+ });
231
+ }
232
+ }
233
+
234
+ export function validateSafeFieldName(field) {
235
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
236
+ throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线`, {
237
+ cause: null,
238
+ code: "validation"
239
+ });
240
+ }
241
+ }
242
+
243
+ export function validatePageLimitRange(prepared, rawTable) {
244
+ if (prepared.page < 1 || prepared.page > 10000) {
245
+ throw new Error(`页码必须在 1 到 10000 之间 (table: ${rawTable}, page: ${prepared.page}, limit: ${prepared.limit})`, {
246
+ cause: null,
247
+ code: "validation"
248
+ });
249
+ }
250
+ if (prepared.limit < 1 || prepared.limit > 1000) {
251
+ throw new Error(`每页数量必须在 1 到 1000 之间 (table: ${rawTable}, page: ${prepared.page}, limit: ${prepared.limit})`, {
252
+ cause: null,
253
+ code: "validation"
254
+ });
255
+ }
256
+ }
257
+
258
+ export function validateNoJoins(joins, message) {
259
+ if (Array.isArray(joins) && joins.length > 0) {
260
+ throw new Error(message, {
261
+ cause: null,
262
+ code: "validation"
263
+ });
264
+ }
265
+ }
266
+
267
+ export function validateIncrementValue(table, field, value) {
268
+ if (typeof value !== "number" || isNaN(value)) {
269
+ throw new Error(`自增值必须是有效的数字 (table: ${table}, field: ${field}, value: ${value})`, {
270
+ cause: null,
271
+ code: "validation"
272
+ });
273
+ }
274
+ }
275
+
276
+ export function validateExecuteSql(sql) {
277
+ if (!isString(sql)) {
278
+ throw new Error(`execute 只接受字符串类型的 SQL,收到类型: ${typeof sql},值: ${JSON.stringify(sql)}`, {
279
+ cause: null,
280
+ code: "validation"
281
+ });
282
+ }
283
+ }
284
+
285
+ export function validateExcludeFieldsResult(resultFields, table, excludeSnakeFields) {
286
+ if (resultFields.length === 0) {
287
+ throw new Error(`排除字段后没有剩余字段可查询。表: ${table}, 排除: ${excludeSnakeFields.join(", ")}`, {
288
+ cause: null,
289
+ code: "validation"
290
+ });
291
+ }
292
+ }
293
+
294
+ export function validateTimeIdValue(id) {
295
+ if (!Number.isFinite(id) || id <= 0) {
296
+ throw new Error(`buildInsertRow(timeId) 失败:id 必须为 > 0 的有限 number (id: ${String(id)})`, {
297
+ cause: null,
298
+ code: "validation"
299
+ });
300
+ }
301
+ }
302
+
303
+ export function validateInsertBatchSize(count, maxBatchSize) {
304
+ if (count > maxBatchSize) {
305
+ throw new Error(`批量插入数量 ${count} 超过最大限制 ${maxBatchSize}`, {
306
+ cause: null,
307
+ code: "validation"
308
+ });
309
+ }
310
+ }
311
+
312
+ export function assertNoExprField(field) {
313
+ if (!isString(field)) {
314
+ return;
315
+ }
316
+ if (!isNonEmptyString(field)) {
317
+ return;
318
+ }
319
+ const trimmed = field.trim();
320
+ if (trimmed.includes("(") || trimmed.includes(")")) {
321
+ throw new Error(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`, {
322
+ cause: null,
323
+ code: "validation"
324
+ });
325
+ }
326
+ }
327
+
328
+ export function assertNoUndefinedInRecord(row, label) {
329
+ for (const [key, value] of Object.entries(row)) {
330
+ if (value === undefined) {
331
+ throw new Error(`${label} 存在 undefined 字段值 (field: ${key})`, {
332
+ cause: null,
333
+ code: "validation"
334
+ });
335
+ }
336
+ }
337
+ }
338
+
339
+ export function assertBatchInsertRowsConsistent(rows, options) {
340
+ if (!Array.isArray(rows)) {
341
+ throw new Error("批量插入 rows 必须是数组", {
342
+ cause: null,
343
+ code: "validation"
344
+ });
345
+ }
346
+ if (rows.length === 0) {
347
+ throw new Error(`插入数据不能为空 (table: ${options.table})`, {
348
+ cause: null,
349
+ code: "validation"
350
+ });
351
+ }
352
+
353
+ const first = rows[0];
354
+ if (!first || typeof first !== "object" || Array.isArray(first)) {
355
+ throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: 0)`, {
356
+ cause: null,
357
+ code: "validation"
358
+ });
359
+ }
360
+
361
+ const fields = Object.keys(first);
362
+ if (fields.length === 0) {
363
+ throw new Error(`插入数据必须至少有一个字段 (table: ${options.table})`, {
364
+ cause: null,
365
+ code: "validation"
366
+ });
367
+ }
368
+
369
+ const fieldSet = new Set(fields);
370
+ for (let i = 0; i < rows.length; i++) {
371
+ const row = rows[i];
372
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
373
+ throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: ${i})`, {
374
+ cause: null,
375
+ code: "validation"
376
+ });
377
+ }
378
+
379
+ const rowKeys = Object.keys(row);
380
+ if (rowKeys.length !== fields.length) {
381
+ throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i})`, {
382
+ cause: null,
383
+ code: "validation"
384
+ });
385
+ }
386
+
387
+ for (const key of rowKeys) {
388
+ if (!fieldSet.has(key)) {
389
+ throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i}, extraField: ${key})`, {
390
+ cause: null,
391
+ code: "validation"
392
+ });
393
+ }
394
+ }
395
+
396
+ for (const field of fields) {
397
+ if (!(field in row)) {
398
+ throw new Error(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
399
+ cause: null,
400
+ code: "validation"
401
+ });
402
+ }
403
+ if (row[field] === undefined) {
404
+ throw new Error(`批量插入字段值不能为 undefined (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
405
+ cause: null,
406
+ code: "validation"
407
+ });
408
+ }
409
+ }
410
+ }
411
+
412
+ return fields;
413
+ }
414
+
415
+ export function parseTableRef(tableRef) {
416
+ if (!isString(tableRef)) {
417
+ throw new Error(`tableRef 必须是字符串 (tableRef: ${String(tableRef)})`, {
418
+ cause: null,
419
+ code: "validation"
420
+ });
421
+ }
422
+
423
+ const trimmed = tableRef.trim();
424
+ if (!isNonEmptyString(tableRef)) {
425
+ throw new Error("tableRef 不能为空", {
426
+ cause: null,
427
+ code: "validation"
428
+ });
429
+ }
430
+
431
+ const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
432
+ if (parts.length === 0) {
433
+ throw new Error("tableRef 不能为空", {
434
+ cause: null,
435
+ code: "validation"
436
+ });
437
+ }
438
+ if (parts.length > 2) {
439
+ throw new Error(`不支持的表引用格式(包含过多片段)。请使用最简形式:table 或 table alias 或 schema.table 或 schema.table alias (tableRef: ${trimmed})`, {
440
+ cause: null,
441
+ code: "validation"
442
+ });
443
+ }
444
+
445
+ const namePart = parts[0];
446
+ if (!isNonEmptyString(namePart)) {
447
+ throw new Error(`tableRef 解析失败:缺少表名 (tableRef: ${trimmed})`, {
448
+ cause: null,
449
+ code: "validation"
450
+ });
451
+ }
452
+
453
+ let aliasPart = null;
454
+ if (parts.length === 2) {
455
+ const alias = parts[1];
456
+ if (!isNonEmptyString(alias)) {
457
+ throw new Error(`tableRef 解析失败:缺少 alias (tableRef: ${trimmed})`, {
458
+ cause: null,
459
+ code: "validation"
460
+ });
461
+ }
462
+ aliasPart = alias;
463
+ }
464
+
465
+ const nameSegments = namePart.split(".");
466
+ if (nameSegments.length > 2) {
467
+ throw new Error(`不支持的表引用格式(schema 层级过深) (tableRef: ${trimmed})`, {
468
+ cause: null,
469
+ code: "validation"
470
+ });
471
+ }
472
+
473
+ if (nameSegments.length === 2) {
474
+ const schema = nameSegments[0];
475
+ const table = nameSegments[1];
476
+ if (!isNonEmptyString(schema)) {
477
+ throw new Error(`tableRef 解析失败:schema 为空 (tableRef: ${trimmed})`, {
478
+ cause: null,
479
+ code: "validation"
480
+ });
481
+ }
482
+ if (!isNonEmptyString(table)) {
483
+ throw new Error(`tableRef 解析失败:table 为空 (tableRef: ${trimmed})`, {
484
+ cause: null,
485
+ code: "validation"
486
+ });
487
+ }
488
+ return { schema: schema, table: table, alias: aliasPart };
489
+ }
490
+
491
+ const table = nameSegments[0];
492
+ if (!isNonEmptyString(table)) {
493
+ throw new Error(`tableRef 解析失败:table 为空 (tableRef: ${trimmed})`, {
494
+ cause: null,
495
+ code: "validation"
496
+ });
497
+ }
498
+
499
+ return { schema: null, table: table, alias: aliasPart };
500
+ }
501
+
502
+ export function validateGeneratedBatchId(id, table, index) {
503
+ if (typeof id !== "number") {
504
+ throw new Error(`批量插入生成 ID 失败:ids[${index}] 不是 number (table: ${table})`, {
505
+ cause: null,
506
+ code: "runtime"
507
+ });
508
+ }
509
+ }
510
+
511
+ export function validateAndClassifyFields(fields) {
512
+ if (!fields || fields.length === 0) {
513
+ return { type: "all", fields: [] };
514
+ }
515
+
516
+ if (fields.some((f) => f === "*")) {
517
+ throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段", {
518
+ cause: null,
519
+ code: "validation"
520
+ });
521
+ }
522
+
523
+ if (fields.some((f) => !isNonEmptyString(f))) {
524
+ throw new Error("fields 不能包含空字符串或无效值", {
525
+ cause: null,
526
+ code: "validation"
527
+ });
528
+ }
529
+
530
+ for (const rawField of fields) {
531
+ const checkField = rawField.startsWith("!") ? rawField.substring(1) : rawField;
532
+ assertNoExprField(checkField);
533
+ }
534
+
535
+ const includeFields = fields.filter((f) => !f.startsWith("!"));
536
+ const excludeFields = fields.filter((f) => f.startsWith("!"));
537
+
538
+ if (includeFields.length > 0 && excludeFields.length === 0) {
539
+ return { type: "include", fields: includeFields };
540
+ }
541
+ if (excludeFields.length > 0 && includeFields.length === 0) {
542
+ return { type: "exclude", fields: excludeFields.map((f) => f.substring(1)) };
543
+ }
544
+
545
+ throw new Error('fields 不能同时包含普通字段和排除字段(! 开头)。只能使用以下3种方式之一:\n1. 空数组 [] 或不传(查询所有)\n2. 全部指定字段 ["id", "name"]\n3. 全部排除字段 ["!password", "!token"]', {
546
+ cause: null,
547
+ code: "validation"
548
+ });
549
+ }