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,113 @@
1
+ import { SqlErrors } from "./errors.js";
2
+ import { isNonEmptyString } from "../../utils/is.js";
3
+ import { SqlCheck } from "./check.js";
4
+
5
+ export function toDeleteInSql(options) {
6
+ if (!isNonEmptyString(options.table)) {
7
+ throw new Error(SqlErrors.TO_DELETE_IN_NEED_TABLE(options.table), {
8
+ cause: null,
9
+ code: "validation"
10
+ });
11
+ }
12
+ if (!isNonEmptyString(options.idField)) {
13
+ throw new Error(SqlErrors.TO_DELETE_IN_NEED_ID_FIELD(options.idField), {
14
+ cause: null,
15
+ code: "validation"
16
+ });
17
+ }
18
+ if (!Array.isArray(options.ids)) {
19
+ throw new Error(SqlErrors.TO_DELETE_IN_NEED_IDS, {
20
+ cause: null,
21
+ code: "validation"
22
+ });
23
+ }
24
+ if (options.ids.length === 0) {
25
+ return { sql: "", params: [] };
26
+ }
27
+
28
+ const placeholders = options.ids.map(() => "?").join(",");
29
+ const sql = `DELETE FROM ${options.quoteIdent(options.table)} WHERE ${options.quoteIdent(options.idField)} IN (${placeholders})`;
30
+ const params = [];
31
+ for (const id of options.ids) {
32
+ params.push(id);
33
+ }
34
+ return { sql: sql, params: params };
35
+ }
36
+
37
+ export function toUpdateCaseByIdSql(options) {
38
+ if (!isNonEmptyString(options.table)) {
39
+ throw new Error(SqlErrors.TO_UPDATE_CASE_NEED_TABLE(options.table), {
40
+ cause: null,
41
+ code: "validation"
42
+ });
43
+ }
44
+ if (!isNonEmptyString(options.idField)) {
45
+ throw new Error(SqlErrors.TO_UPDATE_CASE_NEED_ID_FIELD(options.idField), {
46
+ cause: null,
47
+ code: "validation"
48
+ });
49
+ }
50
+ if (!Array.isArray(options.rows)) {
51
+ throw new Error(SqlErrors.TO_UPDATE_CASE_NEED_ROWS, {
52
+ cause: null,
53
+ code: "validation"
54
+ });
55
+ }
56
+ if (options.rows.length === 0) {
57
+ return { sql: "", params: [] };
58
+ }
59
+ if (!Array.isArray(options.fields)) {
60
+ throw new Error(SqlErrors.TO_UPDATE_CASE_NEED_FIELDS, {
61
+ cause: null,
62
+ code: "validation"
63
+ });
64
+ }
65
+ if (options.fields.length === 0) {
66
+ return { sql: "", params: [] };
67
+ }
68
+
69
+ const ids = options.rows.map((row) => row.id);
70
+ const placeholders = ids.map(() => "?").join(",");
71
+
72
+ const setSqlList = [];
73
+ const args = [];
74
+
75
+ const quotedId = options.quoteIdent(options.idField);
76
+
77
+ for (const field of options.fields) {
78
+ const whenList = [];
79
+
80
+ for (const row of options.rows) {
81
+ if (!(field in row.data)) {
82
+ continue;
83
+ }
84
+
85
+ whenList.push("WHEN ? THEN ?");
86
+ args.push(row.id);
87
+ const value = row.data[field];
88
+ SqlCheck.assertNoUndefinedParam(value, "SQL 参数值");
89
+ args.push(value);
90
+ }
91
+
92
+ if (whenList.length === 0) {
93
+ continue;
94
+ }
95
+
96
+ const quotedField = options.quoteIdent(field);
97
+ setSqlList.push(`${quotedField} = CASE ${quotedId} ${whenList.join(" ")} ELSE ${quotedField} END`);
98
+ }
99
+
100
+ setSqlList.push(`${options.quoteIdent(options.updatedAtField)} = ?`);
101
+ args.push(options.updatedAtValue);
102
+
103
+ for (const id of ids) {
104
+ args.push(id);
105
+ }
106
+
107
+ let sql = `UPDATE ${options.quoteIdent(options.table)} SET ${setSqlList.join(", ")} WHERE ${quotedId} IN (${placeholders})`;
108
+ if (options.stateGtZero && options.stateField) {
109
+ sql += ` AND ${options.quoteIdent(options.stateField)} > 0`;
110
+ }
111
+
112
+ return { sql: sql, params: args };
113
+ }
@@ -0,0 +1,150 @@
1
+ import { isMinLengthString, isNonEmptyString } from "../../utils/is.js";
2
+
3
+ export class SqlCheck {
4
+ static assertNoUndefinedParam(value, label) {
5
+ if (value === undefined) {
6
+ throw new Error(`${label} 不能为 undefined`, {
7
+ cause: null,
8
+ code: "validation"
9
+ });
10
+ }
11
+ }
12
+
13
+ static startsWithQuote(value) {
14
+ const trimmed = value.trim();
15
+ return trimmed.startsWith("`") || trimmed.startsWith('"');
16
+ }
17
+
18
+ static isQuotedIdentPaired(value) {
19
+ const trimmed = value.trim();
20
+ if (!isMinLengthString(trimmed, 2)) {
21
+ return false;
22
+ }
23
+ const first = trimmed[0];
24
+ const last = trimmed[trimmed.length - 1];
25
+ if (first === "`" && last === "`") {
26
+ return true;
27
+ }
28
+ if (first === '"' && last === '"') {
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+
34
+ static assertPairedQuotedIdentIfStartsWithQuote(value, label) {
35
+ if (SqlCheck.startsWithQuote(value) && !SqlCheck.isQuotedIdentPaired(value)) {
36
+ throw new Error(`${label} 引用不完整,请使用成对的 \`...\` 或 "..." (value: ${value})`, {
37
+ cause: null,
38
+ code: "validation"
39
+ });
40
+ }
41
+ }
42
+
43
+ static assertSafeIdentifierPart(part, kind) {
44
+ if (!SqlCheck.SAFE_IDENTIFIER_RE.test(part)) {
45
+ throw new Error(`无效的 ${kind} 标识符: ${part}`, {
46
+ cause: null,
47
+ code: "validation"
48
+ });
49
+ }
50
+ }
51
+
52
+ static assertSafeAlias(aliasPart) {
53
+ if (SqlCheck.isQuotedIdentPaired(aliasPart)) {
54
+ return;
55
+ }
56
+ if (!SqlCheck.SAFE_IDENTIFIER_RE.test(aliasPart)) {
57
+ throw new Error(`无效的字段别名: ${aliasPart}`, {
58
+ cause: null,
59
+ code: "validation"
60
+ });
61
+ }
62
+ }
63
+
64
+ static assertNoExprField(field) {
65
+ if (!isNonEmptyString(field)) {
66
+ return;
67
+ }
68
+ const trimmed = field.trim();
69
+ if (trimmed.includes("(") || trimmed.includes(")")) {
70
+ throw new Error(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`, {
71
+ cause: null,
72
+ code: "validation"
73
+ });
74
+ }
75
+ }
76
+
77
+ static assertBatchInsertRowsConsistent(rows, options) {
78
+ if (!Array.isArray(rows)) {
79
+ throw new Error("批量插入 rows 必须是数组", {
80
+ cause: null,
81
+ code: "validation"
82
+ });
83
+ }
84
+ if (rows.length === 0) {
85
+ throw new Error(`插入数据不能为空 (table: ${options.table})`, {
86
+ cause: null,
87
+ code: "validation"
88
+ });
89
+ }
90
+
91
+ const first = rows[0];
92
+ if (!first || typeof first !== "object" || Array.isArray(first)) {
93
+ throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: 0)`, {
94
+ cause: null,
95
+ code: "validation"
96
+ });
97
+ }
98
+
99
+ const fields = Object.keys(first);
100
+ if (fields.length === 0) {
101
+ throw new Error(`插入数据必须至少有一个字段 (table: ${options.table})`, {
102
+ cause: null,
103
+ code: "validation"
104
+ });
105
+ }
106
+
107
+ const fieldSet = new Set(fields);
108
+
109
+ for (let i = 0; i < rows.length; i++) {
110
+ const row = rows[i];
111
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
112
+ throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: ${i})`, {
113
+ cause: null,
114
+ code: "validation"
115
+ });
116
+ }
117
+
118
+ const rowKeys = Object.keys(row);
119
+ if (rowKeys.length !== fields.length) {
120
+ throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i})`, {
121
+ cause: null,
122
+ code: "validation"
123
+ });
124
+ }
125
+
126
+ for (const key of rowKeys) {
127
+ if (!fieldSet.has(key)) {
128
+ throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i}, extraField: ${key})`, {
129
+ cause: null,
130
+ code: "validation"
131
+ });
132
+ }
133
+ }
134
+
135
+ for (const field of fields) {
136
+ if (!(field in row)) {
137
+ throw new Error(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
138
+ cause: null,
139
+ code: "validation"
140
+ });
141
+ }
142
+ SqlCheck.assertNoUndefinedParam(row[field], `批量插入字段值 (table: ${options.table}, rowIndex: ${i}, field: ${field})`);
143
+ }
144
+ }
145
+
146
+ return fields;
147
+ }
148
+ }
149
+
150
+ SqlCheck.SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -0,0 +1,347 @@
1
+ import { SqlCheck } from "./check.js";
2
+ import { SqlErrors } from "./errors.js";
3
+ import { escapeField, escapeTable, validateParam } from "./util.js";
4
+ import { isString } from "../../utils/is.js";
5
+
6
+ export function compileSelect(model, quoteIdent) {
7
+ let sql = "SELECT ";
8
+ const params = [];
9
+
10
+ if (model.select.length > 0) {
11
+ const fields = model.select.map((item) => {
12
+ if (item.type === "raw") {
13
+ return item.value;
14
+ }
15
+ return escapeField(item.value, quoteIdent);
16
+ });
17
+ sql += fields.join(", ");
18
+ } else {
19
+ sql += "*";
20
+ }
21
+
22
+ if (!model.from) {
23
+ throw new Error(SqlErrors.FROM_REQUIRED, {
24
+ cause: null,
25
+ code: "validation"
26
+ });
27
+ }
28
+
29
+ const fromSql = model.from.type === "raw" ? model.from.value : escapeTable(model.from.value, quoteIdent);
30
+ sql += ` FROM ${fromSql}`;
31
+
32
+ if (model.joins.length > 0) {
33
+ const joinSql = model.joins
34
+ .map((join) => {
35
+ const joinTable = escapeTable(join.table, quoteIdent);
36
+ const joinType = join.type.toUpperCase();
37
+ return `${joinType} JOIN ${joinTable} ON ${join.on}`;
38
+ })
39
+ .join(" ");
40
+ sql += ` ${joinSql}`;
41
+ }
42
+
43
+ const whereResult = compileWhere(model.where, quoteIdent);
44
+ if (whereResult.sql) {
45
+ sql += ` WHERE ${whereResult.sql}`;
46
+ for (const param of whereResult.params) {
47
+ params.push(param);
48
+ }
49
+ }
50
+
51
+ if (model.groupBy.length > 0) {
52
+ const groupSql = model.groupBy.map((field) => escapeField(field, quoteIdent)).join(", ");
53
+ sql += ` GROUP BY ${groupSql}`;
54
+ }
55
+
56
+ if (model.having.length > 0) {
57
+ sql += ` HAVING ${model.having.join(" AND ")}`;
58
+ }
59
+
60
+ if (model.orderBy.length > 0) {
61
+ const orderSql = model.orderBy
62
+ .map((item) => {
63
+ const escapedField = escapeField(item.field, quoteIdent);
64
+ return `${escapedField} ${item.dir}`;
65
+ })
66
+ .join(", ");
67
+ sql += ` ORDER BY ${orderSql}`;
68
+ }
69
+
70
+ if (model.limit !== null) {
71
+ sql += ` LIMIT ${model.limit}`;
72
+ if (model.offset !== null) {
73
+ sql += ` OFFSET ${model.offset}`;
74
+ }
75
+ }
76
+
77
+ return { sql: sql, params: params };
78
+ }
79
+
80
+ export function compileCount(model, quoteIdent) {
81
+ let sql = "SELECT COUNT(*) as total";
82
+ const params = [];
83
+
84
+ if (!model.from) {
85
+ throw new Error(SqlErrors.COUNT_NEED_FROM, {
86
+ cause: null,
87
+ code: "validation"
88
+ });
89
+ }
90
+
91
+ const fromSql = model.from.type === "raw" ? model.from.value : escapeTable(model.from.value, quoteIdent);
92
+ sql += ` FROM ${fromSql}`;
93
+
94
+ if (model.joins.length > 0) {
95
+ const joinSql = model.joins
96
+ .map((join) => {
97
+ const joinTable = escapeTable(join.table, quoteIdent);
98
+ const joinType = join.type.toUpperCase();
99
+ return `${joinType} JOIN ${joinTable} ON ${join.on}`;
100
+ })
101
+ .join(" ");
102
+ sql += ` ${joinSql}`;
103
+ }
104
+
105
+ const whereResult = compileWhere(model.where, quoteIdent);
106
+ if (whereResult.sql) {
107
+ sql += ` WHERE ${whereResult.sql}`;
108
+ for (const param of whereResult.params) {
109
+ params.push(param);
110
+ }
111
+ }
112
+
113
+ return { sql: sql, params: params };
114
+ }
115
+
116
+ export function compileInsert(table, data, quoteIdent) {
117
+ if (!table || !isString(table)) {
118
+ throw new Error(SqlErrors.INSERT_NEED_TABLE(table), {
119
+ cause: null,
120
+ code: "validation"
121
+ });
122
+ }
123
+
124
+ if (!data || typeof data !== "object") {
125
+ throw new Error(SqlErrors.INSERT_NEED_DATA(table, data), {
126
+ cause: null,
127
+ code: "validation"
128
+ });
129
+ }
130
+
131
+ const escapedTable = escapeTable(table, quoteIdent);
132
+
133
+ if (Array.isArray(data)) {
134
+ const fields = SqlCheck.assertBatchInsertRowsConsistent(data, { table: table });
135
+
136
+ const escapedFields = fields.map((field) => escapeField(field, quoteIdent));
137
+ const placeholders = fields.map(() => "?").join(", ");
138
+ const values = data.map(() => `(${placeholders})`).join(", ");
139
+
140
+ const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES ${values}`;
141
+ const params = [];
142
+ for (let i = 0; i < data.length; i++) {
143
+ const row = data[i];
144
+ for (const field of fields) {
145
+ const value = row[field];
146
+ validateParam(value);
147
+ params.push(value);
148
+ }
149
+ }
150
+
151
+ return { sql: sql, params: params };
152
+ }
153
+
154
+ const fields = Object.keys(data);
155
+ if (fields.length === 0) {
156
+ throw new Error(SqlErrors.INSERT_NEED_AT_LEAST_ONE_FIELD(table), {
157
+ cause: null,
158
+ code: "validation"
159
+ });
160
+ }
161
+
162
+ for (const field of fields) {
163
+ validateParam(data[field]);
164
+ }
165
+
166
+ const escapedFields = fields.map((field) => escapeField(field, quoteIdent));
167
+ const placeholders = fields.map(() => "?").join(", ");
168
+ const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`;
169
+ const params = [];
170
+ for (const field of fields) {
171
+ params.push(data[field]);
172
+ }
173
+
174
+ return { sql: sql, params: params };
175
+ }
176
+
177
+ export function compileUpdate(model, table, data, quoteIdent) {
178
+ if (!table || !isString(table)) {
179
+ throw new Error(SqlErrors.UPDATE_NEED_TABLE, {
180
+ cause: null,
181
+ code: "validation"
182
+ });
183
+ }
184
+
185
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
186
+ throw new Error(SqlErrors.UPDATE_NEED_OBJECT, {
187
+ cause: null,
188
+ code: "validation"
189
+ });
190
+ }
191
+
192
+ const fields = Object.keys(data);
193
+ if (fields.length === 0) {
194
+ throw new Error(SqlErrors.UPDATE_NEED_AT_LEAST_ONE_FIELD, {
195
+ cause: null,
196
+ code: "validation"
197
+ });
198
+ }
199
+
200
+ const escapedTable = escapeTable(table, quoteIdent);
201
+ const setFields = fields.map((field) => `${escapeField(field, quoteIdent)} = ?`);
202
+ const params = [];
203
+ for (const value of Object.values(data)) {
204
+ params.push(value);
205
+ }
206
+
207
+ let sql = `UPDATE ${escapedTable} SET ${setFields.join(", ")}`;
208
+
209
+ const whereResult = compileWhere(model.where, quoteIdent);
210
+ if (whereResult.sql) {
211
+ sql += ` WHERE ${whereResult.sql}`;
212
+ for (const param of whereResult.params) {
213
+ params.push(param);
214
+ }
215
+ } else {
216
+ throw new Error(SqlErrors.UPDATE_NEED_WHERE, {
217
+ cause: null,
218
+ code: "validation"
219
+ });
220
+ }
221
+
222
+ return { sql: sql, params: params };
223
+ }
224
+
225
+ export function compileDelete(model, table, quoteIdent) {
226
+ if (!table || !isString(table)) {
227
+ throw new Error(SqlErrors.DELETE_NEED_TABLE, {
228
+ cause: null,
229
+ code: "validation"
230
+ });
231
+ }
232
+
233
+ const escapedTable = escapeTable(table, quoteIdent);
234
+ let sql = `DELETE FROM ${escapedTable}`;
235
+
236
+ const whereResult = compileWhere(model.where, quoteIdent);
237
+ if (whereResult.sql) {
238
+ sql += ` WHERE ${whereResult.sql}`;
239
+ } else {
240
+ throw new Error(SqlErrors.DELETE_NEED_WHERE, {
241
+ cause: null,
242
+ code: "validation"
243
+ });
244
+ }
245
+
246
+ return { sql: sql, params: whereResult.params };
247
+ }
248
+
249
+ export function compileWhere(node, quoteIdent) {
250
+ if (!node) {
251
+ return { sql: "", params: [] };
252
+ }
253
+
254
+ if (node.type === "raw") {
255
+ const params = Array.isArray(node.params) ? node.params.slice() : [];
256
+ return { sql: node.sql, params: params };
257
+ }
258
+
259
+ if (node.type === "op") {
260
+ return compileOperatorNode(node, quoteIdent);
261
+ }
262
+
263
+ if (node.type === "group") {
264
+ if (!node.items || node.items.length === 0) {
265
+ return { sql: "", params: [] };
266
+ }
267
+ const parts = [];
268
+ const params = [];
269
+ for (const item of node.items) {
270
+ const built = compileWhere(item, quoteIdent);
271
+ if (!built.sql) {
272
+ continue;
273
+ }
274
+ let clause = built.sql;
275
+ if (node.join === "OR") {
276
+ clause = `(${clause})`;
277
+ } else if (item.type === "group" && item.join === "OR") {
278
+ clause = `(${clause})`;
279
+ }
280
+ parts.push(clause);
281
+ for (const param of built.params) {
282
+ params.push(param);
283
+ }
284
+ }
285
+ return { sql: parts.join(` ${node.join} `), params: params };
286
+ }
287
+
288
+ return { sql: "", params: [] };
289
+ }
290
+
291
+ function compileOperatorNode(node, quoteIdent) {
292
+ const escapedField = escapeField(node.field, quoteIdent);
293
+ const operator = node.operator;
294
+
295
+ switch (operator) {
296
+ case "$ne":
297
+ case "$not":
298
+ validateParam(node.value);
299
+ return { sql: `${escapedField} != ?`, params: [node.value] };
300
+ case "$in": {
301
+ const placeholders = node.value.map(() => "?").join(",");
302
+ const params = [];
303
+ for (const item of node.value) {
304
+ params.push(item);
305
+ }
306
+ return { sql: `${escapedField} IN (${placeholders})`, params: params };
307
+ }
308
+ case "$nin":
309
+ case "$notIn": {
310
+ const placeholders = node.value.map(() => "?").join(",");
311
+ const params = [];
312
+ for (const item of node.value) {
313
+ params.push(item);
314
+ }
315
+ return { sql: `${escapedField} NOT IN (${placeholders})`, params: params };
316
+ }
317
+ case "$like":
318
+ validateParam(node.value);
319
+ return { sql: `${escapedField} LIKE ?`, params: [node.value] };
320
+ case "$notLike":
321
+ validateParam(node.value);
322
+ return { sql: `${escapedField} NOT LIKE ?`, params: [node.value] };
323
+ case "$gt":
324
+ validateParam(node.value);
325
+ return { sql: `${escapedField} > ?`, params: [node.value] };
326
+ case "$gte":
327
+ validateParam(node.value);
328
+ return { sql: `${escapedField} >= ?`, params: [node.value] };
329
+ case "$lt":
330
+ validateParam(node.value);
331
+ return { sql: `${escapedField} < ?`, params: [node.value] };
332
+ case "$lte":
333
+ validateParam(node.value);
334
+ return { sql: `${escapedField} <= ?`, params: [node.value] };
335
+ case "$between":
336
+ return { sql: `${escapedField} BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
337
+ case "$notBetween":
338
+ return { sql: `${escapedField} NOT BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
339
+ case "$null":
340
+ return { sql: `${escapedField} IS NULL`, params: [] };
341
+ case "$notNull":
342
+ return { sql: `${escapedField} IS NOT NULL`, params: [] };
343
+ default:
344
+ validateParam(node.value);
345
+ return { sql: `${escapedField} = ?`, params: [node.value] };
346
+ }
347
+ }
@@ -0,0 +1,60 @@
1
+ export const SqlErrors = {
2
+ QUOTE_IDENT_NEED_STRING: (identifier) => `quoteIdent 需要字符串类型标识符 (identifier: ${String(identifier)})`,
3
+ IDENT_EMPTY: "SQL 标识符不能为空",
4
+
5
+ FIELD_EXPR_NOT_ALLOWED: (field) => `字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${field})`,
6
+ FIELD_INVALID: (field) => `字段格式非法,请使用简单字段名或安全引用,复杂表达式请使用 selectRaw/whereRaw (field: ${field})`,
7
+
8
+ FROM_EMPTY: "FROM 表名不能为空",
9
+ FROM_NEED_NON_EMPTY: (table) => `FROM 表名必须是非空字符串 (table: ${String(table)})`,
10
+ FROM_REQUIRED: "FROM 表名是必需的",
11
+ COUNT_NEED_FROM: "COUNT 需要 FROM 表名",
12
+
13
+ TABLE_REF_TOO_MANY_PARTS: (table) => `不支持的表引用格式(包含过多片段)。请使用 fromRaw 显式传入复杂表达式 (table: ${table})`,
14
+ TABLE_REF_SCHEMA_TOO_DEEP: (table) => `不支持的表引用格式(schema 层级过深)。请使用 fromRaw (table: ${table})`,
15
+ SCHEMA_QUOTE_NOT_PAIRED: (schema) => `schema 标识符引用不完整,请使用成对的 \`...\` 或 "..." (schema: ${schema})`,
16
+ TABLE_QUOTE_NOT_PAIRED: (tableName) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${tableName})`,
17
+
18
+ SELECT_FIELDS_INVALID: "SELECT 字段必须是字符串或数组",
19
+ SELECT_RAW_NEED_NON_EMPTY: (expr) => `selectRaw 需要非空字符串 (expr: ${String(expr)})`,
20
+ FROM_RAW_NEED_NON_EMPTY: (tableExpr) => `fromRaw 需要非空字符串 (tableExpr: ${String(tableExpr)})`,
21
+ WHERE_RAW_NEED_NON_EMPTY: (sql) => `whereRaw 需要非空字符串 (sql: ${String(sql)})`,
22
+ WHERE_VALUE_REQUIRED: "where(field, value) 不允许省略 value。若需传入原始 WHERE,请使用 whereRaw",
23
+
24
+ JOIN_NEED_STRING: (table, on) => `JOIN 表名和条件必须是字符串 (table: ${String(table)}, on: ${String(on)})`,
25
+
26
+ ORDER_BY_NEED_ARRAY: 'orderBy 必须是字符串数组,格式为 "字段#方向"',
27
+ ORDER_BY_ITEM_NEED_HASH: (item) => `orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`,
28
+ ORDER_BY_FIELD_EMPTY: (item) => `orderBy 中字段名不能为空 (item: ${item})`,
29
+ ORDER_BY_DIR_INVALID: (direction) => `ORDER BY 方向必须是 ASC 或 DESC (direction: ${direction})`,
30
+
31
+ LIMIT_MUST_NON_NEGATIVE: (count) => `LIMIT 数量必须是非负数 (count: ${String(count)})`,
32
+ OFFSET_MUST_NON_NEGATIVE: (offset) => `OFFSET 必须是非负数 (offset: ${String(offset)})`,
33
+ OFFSET_COUNT_MUST_NON_NEGATIVE: (count) => `OFFSET 必须是非负数 (count: ${String(count)})`,
34
+
35
+ INSERT_NEED_TABLE: (table) => `INSERT 需要表名 (table: ${String(table)})`,
36
+ INSERT_NEED_DATA: (table, data) => `INSERT 需要数据 (table: ${String(table)}, data: ${JSON.stringify(data)})`,
37
+ INSERT_NEED_AT_LEAST_ONE_FIELD: (table) => `插入数据必须至少有一个字段 (table: ${table})`,
38
+
39
+ UPDATE_NEED_TABLE: "UPDATE 需要表名",
40
+ UPDATE_NEED_OBJECT: "UPDATE 需要数据对象",
41
+ UPDATE_NEED_AT_LEAST_ONE_FIELD: "更新数据必须至少有一个字段",
42
+ UPDATE_NEED_WHERE: "为安全起见,UPDATE 需要 WHERE 条件",
43
+
44
+ DELETE_NEED_TABLE: "DELETE 需要表名",
45
+ DELETE_NEED_WHERE: "为安全起见,DELETE 需要 WHERE 条件",
46
+
47
+ TO_DELETE_IN_NEED_TABLE: (table) => `toDeleteInSql 需要非空表名 (table: ${String(table)})`,
48
+ TO_DELETE_IN_NEED_ID_FIELD: (idField) => `toDeleteInSql 需要非空 idField (idField: ${String(idField)})`,
49
+ TO_DELETE_IN_NEED_IDS: "toDeleteInSql 需要 ids 数组",
50
+
51
+ TO_UPDATE_CASE_NEED_TABLE: (table) => `toUpdateCaseByIdSql 需要非空表名 (table: ${String(table)})`,
52
+ TO_UPDATE_CASE_NEED_ID_FIELD: (idField) => `toUpdateCaseByIdSql 需要非空 idField (idField: ${String(idField)})`,
53
+ TO_UPDATE_CASE_NEED_ROWS: "toUpdateCaseByIdSql 需要 rows 数组",
54
+ TO_UPDATE_CASE_NEED_FIELDS: "toUpdateCaseByIdSql 需要 fields 数组",
55
+
56
+ IN_NEED_ARRAY: (operator) => `$in 操作符的值必须是数组 (operator: ${operator})`,
57
+ IN_NEED_NON_EMPTY: "$in 操作符的数组不能为空。提示:空数组会导致查询永远不匹配任何记录,这通常不是预期行为。请检查查询条件或移除该字段。",
58
+ NIN_NEED_ARRAY: (operator) => `$nin/$notIn 操作符的值必须是数组 (operator: ${operator})`,
59
+ NIN_NEED_NON_EMPTY: "$nin/$notIn 操作符的数组不能为空。提示:空数组会导致查询匹配所有记录,这通常不是预期行为。请检查查询条件或移除该字段。"
60
+ };