befly 3.16.10 → 3.16.11

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 (178) hide show
  1. package/README.md +0 -129
  2. package/befly.js +12769 -0
  3. package/befly.min.js +47 -0
  4. package/package.json +18 -29
  5. package/dist/befly.config.d.ts +0 -7
  6. package/dist/befly.config.js +0 -128
  7. package/dist/befly.js +0 -17348
  8. package/dist/befly.min.js +0 -23
  9. package/dist/checks/checkApi.d.ts +0 -1
  10. package/dist/checks/checkApi.js +0 -139
  11. package/dist/checks/checkConfig.d.ts +0 -9
  12. package/dist/checks/checkConfig.js +0 -255
  13. package/dist/checks/checkHook.d.ts +0 -1
  14. package/dist/checks/checkHook.js +0 -132
  15. package/dist/checks/checkMenu.d.ts +0 -3
  16. package/dist/checks/checkMenu.js +0 -106
  17. package/dist/checks/checkPlugin.d.ts +0 -1
  18. package/dist/checks/checkPlugin.js +0 -132
  19. package/dist/checks/checkTable.d.ts +0 -7
  20. package/dist/checks/checkTable.js +0 -431
  21. package/dist/configs/presetRegexp.d.ts +0 -145
  22. package/dist/configs/presetRegexp.js +0 -218
  23. package/dist/hooks/auth.d.ts +0 -3
  24. package/dist/hooks/auth.js +0 -24
  25. package/dist/hooks/cors.d.ts +0 -7
  26. package/dist/hooks/cors.js +0 -36
  27. package/dist/hooks/parser.d.ts +0 -10
  28. package/dist/hooks/parser.js +0 -76
  29. package/dist/hooks/permission.d.ts +0 -11
  30. package/dist/hooks/permission.js +0 -78
  31. package/dist/hooks/validator.d.ts +0 -7
  32. package/dist/hooks/validator.js +0 -52
  33. package/dist/index.d.ts +0 -28
  34. package/dist/index.js +0 -316
  35. package/dist/lib/asyncContext.d.ts +0 -21
  36. package/dist/lib/asyncContext.js +0 -27
  37. package/dist/lib/cacheHelper.d.ts +0 -128
  38. package/dist/lib/cacheHelper.js +0 -477
  39. package/dist/lib/cacheKeys.d.ts +0 -27
  40. package/dist/lib/cacheKeys.js +0 -37
  41. package/dist/lib/cipher.d.ts +0 -153
  42. package/dist/lib/cipher.js +0 -237
  43. package/dist/lib/connect.d.ts +0 -95
  44. package/dist/lib/connect.js +0 -313
  45. package/dist/lib/dbHelper.d.ts +0 -229
  46. package/dist/lib/dbHelper.js +0 -1099
  47. package/dist/lib/dbUtils.d.ts +0 -91
  48. package/dist/lib/dbUtils.js +0 -544
  49. package/dist/lib/jwt.d.ts +0 -13
  50. package/dist/lib/jwt.js +0 -77
  51. package/dist/lib/logger.d.ts +0 -46
  52. package/dist/lib/logger.js +0 -731
  53. package/dist/lib/redisHelper.d.ts +0 -193
  54. package/dist/lib/redisHelper.js +0 -598
  55. package/dist/lib/sqlBuilder.d.ts +0 -160
  56. package/dist/lib/sqlBuilder.js +0 -837
  57. package/dist/lib/sqlCheck.d.ts +0 -23
  58. package/dist/lib/sqlCheck.js +0 -119
  59. package/dist/lib/validator.d.ts +0 -45
  60. package/dist/lib/validator.js +0 -424
  61. package/dist/loader/loadApis.d.ts +0 -12
  62. package/dist/loader/loadApis.js +0 -71
  63. package/dist/loader/loadHooks.d.ts +0 -7
  64. package/dist/loader/loadHooks.js +0 -50
  65. package/dist/loader/loadPlugins.d.ts +0 -8
  66. package/dist/loader/loadPlugins.js +0 -69
  67. package/dist/paths.d.ts +0 -93
  68. package/dist/paths.js +0 -100
  69. package/dist/plugins/cache.d.ts +0 -10
  70. package/dist/plugins/cache.js +0 -24
  71. package/dist/plugins/cipher.d.ts +0 -7
  72. package/dist/plugins/cipher.js +0 -14
  73. package/dist/plugins/config.d.ts +0 -3
  74. package/dist/plugins/config.js +0 -9
  75. package/dist/plugins/db.d.ts +0 -10
  76. package/dist/plugins/db.js +0 -48
  77. package/dist/plugins/jwt.d.ts +0 -6
  78. package/dist/plugins/jwt.js +0 -13
  79. package/dist/plugins/logger.d.ts +0 -10
  80. package/dist/plugins/logger.js +0 -21
  81. package/dist/plugins/redis.d.ts +0 -10
  82. package/dist/plugins/redis.js +0 -40
  83. package/dist/plugins/tool.d.ts +0 -75
  84. package/dist/plugins/tool.js +0 -105
  85. package/dist/router/api.d.ts +0 -14
  86. package/dist/router/api.js +0 -109
  87. package/dist/router/static.d.ts +0 -9
  88. package/dist/router/static.js +0 -56
  89. package/dist/scripts/ensureDist.d.ts +0 -1
  90. package/dist/scripts/ensureDist.js +0 -296
  91. package/dist/sync/syncApi.d.ts +0 -3
  92. package/dist/sync/syncApi.js +0 -163
  93. package/dist/sync/syncCache.d.ts +0 -2
  94. package/dist/sync/syncCache.js +0 -14
  95. package/dist/sync/syncDev.d.ts +0 -6
  96. package/dist/sync/syncDev.js +0 -166
  97. package/dist/sync/syncMenu.d.ts +0 -14
  98. package/dist/sync/syncMenu.js +0 -308
  99. package/dist/sync/syncTable.d.ts +0 -126
  100. package/dist/sync/syncTable.js +0 -1129
  101. package/dist/types/api.d.ts +0 -177
  102. package/dist/types/api.js +0 -4
  103. package/dist/types/befly.d.ts +0 -231
  104. package/dist/types/befly.js +0 -4
  105. package/dist/types/cache.d.ts +0 -96
  106. package/dist/types/cache.js +0 -4
  107. package/dist/types/cipher.d.ts +0 -27
  108. package/dist/types/cipher.js +0 -7
  109. package/dist/types/common.d.ts +0 -127
  110. package/dist/types/common.js +0 -5
  111. package/dist/types/context.d.ts +0 -39
  112. package/dist/types/context.js +0 -4
  113. package/dist/types/coreError.d.ts +0 -31
  114. package/dist/types/coreError.js +0 -38
  115. package/dist/types/crypto.d.ts +0 -20
  116. package/dist/types/crypto.js +0 -4
  117. package/dist/types/database.d.ts +0 -182
  118. package/dist/types/database.js +0 -4
  119. package/dist/types/hook.d.ts +0 -30
  120. package/dist/types/hook.js +0 -19
  121. package/dist/types/jwt.d.ts +0 -76
  122. package/dist/types/jwt.js +0 -4
  123. package/dist/types/logger.d.ts +0 -95
  124. package/dist/types/logger.js +0 -6
  125. package/dist/types/plugin.d.ts +0 -27
  126. package/dist/types/plugin.js +0 -17
  127. package/dist/types/redis.d.ts +0 -80
  128. package/dist/types/redis.js +0 -4
  129. package/dist/types/roleApisCache.d.ts +0 -21
  130. package/dist/types/roleApisCache.js +0 -8
  131. package/dist/types/sync.d.ts +0 -93
  132. package/dist/types/sync.js +0 -4
  133. package/dist/types/table.d.ts +0 -34
  134. package/dist/types/table.js +0 -4
  135. package/dist/types/validate.d.ts +0 -113
  136. package/dist/types/validate.js +0 -4
  137. package/dist/utils/calcPerfTime.d.ts +0 -4
  138. package/dist/utils/calcPerfTime.js +0 -13
  139. package/dist/utils/cors.d.ts +0 -8
  140. package/dist/utils/cors.js +0 -17
  141. package/dist/utils/dbFieldRules.d.ts +0 -31
  142. package/dist/utils/dbFieldRules.js +0 -94
  143. package/dist/utils/fieldClear.d.ts +0 -11
  144. package/dist/utils/fieldClear.js +0 -57
  145. package/dist/utils/formatYmdHms.d.ts +0 -1
  146. package/dist/utils/formatYmdHms.js +0 -20
  147. package/dist/utils/getClientIp.d.ts +0 -6
  148. package/dist/utils/getClientIp.js +0 -39
  149. package/dist/utils/importDefault.d.ts +0 -1
  150. package/dist/utils/importDefault.js +0 -53
  151. package/dist/utils/isDirentDirectory.d.ts +0 -3
  152. package/dist/utils/isDirentDirectory.js +0 -18
  153. package/dist/utils/loadMenuConfigs.d.ts +0 -11
  154. package/dist/utils/loadMenuConfigs.js +0 -130
  155. package/dist/utils/loggerUtils.d.ts +0 -18
  156. package/dist/utils/loggerUtils.js +0 -171
  157. package/dist/utils/mergeAndConcat.d.ts +0 -7
  158. package/dist/utils/mergeAndConcat.js +0 -77
  159. package/dist/utils/normalizeFieldDefinition.d.ts +0 -18
  160. package/dist/utils/normalizeFieldDefinition.js +0 -27
  161. package/dist/utils/processInfo.d.ts +0 -26
  162. package/dist/utils/processInfo.js +0 -41
  163. package/dist/utils/response.d.ts +0 -20
  164. package/dist/utils/response.js +0 -96
  165. package/dist/utils/scanAddons.d.ts +0 -15
  166. package/dist/utils/scanAddons.js +0 -35
  167. package/dist/utils/scanCoreBuiltins.d.ts +0 -3
  168. package/dist/utils/scanCoreBuiltins.js +0 -72
  169. package/dist/utils/scanFiles.d.ts +0 -32
  170. package/dist/utils/scanFiles.js +0 -124
  171. package/dist/utils/scanSources.d.ts +0 -10
  172. package/dist/utils/scanSources.js +0 -46
  173. package/dist/utils/sortModules.d.ts +0 -28
  174. package/dist/utils/sortModules.js +0 -105
  175. package/dist/utils/sqlUtil.d.ts +0 -33
  176. package/dist/utils/sqlUtil.js +0 -146
  177. package/dist/utils/util.d.ts +0 -172
  178. package/dist/utils/util.js +0 -517
@@ -1,837 +0,0 @@
1
- /**
2
- * SQL 构造器 - TypeScript 版本
3
- * 提供链式 API 构建 SQL 查询
4
- */
5
- import { SqlCheck } from "./sqlCheck";
6
- const SqlBuilderError = {
7
- QUOTE_IDENT_NEED_STRING: (identifier) => `quoteIdent 需要字符串类型标识符 (identifier: ${String(identifier)})`,
8
- IDENT_EMPTY: "SQL 标识符不能为空",
9
- FIELD_EXPR_NOT_ALLOWED: (field) => `字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${field})`,
10
- FIELD_INVALID: (field) => `字段格式非法,请使用简单字段名或安全引用,复杂表达式请使用 selectRaw/whereRaw (field: ${field})`,
11
- FROM_EMPTY: "FROM 表名不能为空",
12
- FROM_NEED_NON_EMPTY: (table) => `FROM 表名必须是非空字符串 (table: ${String(table)})`,
13
- FROM_REQUIRED: "FROM 表名是必需的",
14
- COUNT_NEED_FROM: "COUNT 需要 FROM 表名",
15
- TABLE_REF_TOO_MANY_PARTS: (table) => `不支持的表引用格式(包含过多片段)。请使用 fromRaw 显式传入复杂表达式 (table: ${table})`,
16
- TABLE_REF_SCHEMA_TOO_DEEP: (table) => `不支持的表引用格式(schema 层级过深)。请使用 fromRaw (table: ${table})`,
17
- SCHEMA_QUOTE_NOT_PAIRED: (schema) => `schema 标识符引用不完整,请使用成对的 \`...\` 或 "..." (schema: ${schema})`,
18
- TABLE_QUOTE_NOT_PAIRED: (tableName) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${tableName})`,
19
- SELECT_FIELDS_INVALID: "SELECT 字段必须是字符串或数组",
20
- SELECT_RAW_NEED_NON_EMPTY: (expr) => `selectRaw 需要非空字符串 (expr: ${String(expr)})`,
21
- FROM_RAW_NEED_NON_EMPTY: (tableExpr) => `fromRaw 需要非空字符串 (tableExpr: ${String(tableExpr)})`,
22
- WHERE_RAW_NEED_NON_EMPTY: (sql) => `whereRaw 需要非空字符串 (sql: ${String(sql)})`,
23
- WHERE_VALUE_REQUIRED: "where(field, value) 不允许省略 value。若需传入原始 WHERE,请使用 whereRaw",
24
- JOIN_NEED_STRING: (table, on) => `JOIN 表名和条件必须是字符串 (table: ${String(table)}, on: ${String(on)})`,
25
- ORDER_BY_NEED_ARRAY: 'orderBy 必须是字符串数组,格式为 "字段#方向"',
26
- ORDER_BY_ITEM_NEED_HASH: (item) => `orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`,
27
- ORDER_BY_FIELD_EMPTY: (item) => `orderBy 中字段名不能为空 (item: ${item})`,
28
- ORDER_BY_DIR_INVALID: (direction) => `ORDER BY 方向必须是 ASC 或 DESC (direction: ${direction})`,
29
- LIMIT_MUST_NON_NEGATIVE: (count) => `LIMIT 数量必须是非负数 (count: ${String(count)})`,
30
- OFFSET_MUST_NON_NEGATIVE: (offset) => `OFFSET 必须是非负数 (offset: ${String(offset)})`,
31
- OFFSET_COUNT_MUST_NON_NEGATIVE: (count) => `OFFSET 必须是非负数 (count: ${String(count)})`,
32
- INSERT_NEED_TABLE: (table) => `INSERT 需要表名 (table: ${String(table)})`,
33
- INSERT_NEED_DATA: (table, data) => `INSERT 需要数据 (table: ${String(table)}, data: ${JSON.stringify(data)})`,
34
- INSERT_NEED_AT_LEAST_ONE_FIELD: (table) => `插入数据必须至少有一个字段 (table: ${table})`,
35
- UPDATE_NEED_TABLE: "UPDATE 需要表名",
36
- UPDATE_NEED_OBJECT: "UPDATE 需要数据对象",
37
- UPDATE_NEED_AT_LEAST_ONE_FIELD: "更新数据必须至少有一个字段",
38
- UPDATE_NEED_WHERE: "为安全起见,UPDATE 需要 WHERE 条件",
39
- DELETE_NEED_TABLE: "DELETE 需要表名",
40
- DELETE_NEED_WHERE: "为安全起见,DELETE 需要 WHERE 条件",
41
- TO_DELETE_IN_NEED_TABLE: (table) => `toDeleteInSql 需要非空表名 (table: ${String(table)})`,
42
- TO_DELETE_IN_NEED_ID_FIELD: (idField) => `toDeleteInSql 需要非空 idField (idField: ${String(idField)})`,
43
- TO_DELETE_IN_NEED_IDS: "toDeleteInSql 需要 ids 数组",
44
- TO_UPDATE_CASE_NEED_TABLE: (table) => `toUpdateCaseByIdSql 需要非空表名 (table: ${String(table)})`,
45
- TO_UPDATE_CASE_NEED_ID_FIELD: (idField) => `toUpdateCaseByIdSql 需要非空 idField (idField: ${String(idField)})`,
46
- TO_UPDATE_CASE_NEED_ROWS: "toUpdateCaseByIdSql 需要 rows 数组",
47
- TO_UPDATE_CASE_NEED_FIELDS: "toUpdateCaseByIdSql 需要 fields 数组"
48
- };
49
- /**
50
- * SQL 构建器类
51
- */
52
- export class SqlBuilder {
53
- _select = [];
54
- _from = "";
55
- _where = [];
56
- _joins = [];
57
- _orderBy = [];
58
- _groupBy = [];
59
- _having = [];
60
- _limit = null;
61
- _offset = null;
62
- _params = [];
63
- _quoteIdent;
64
- constructor(options) {
65
- if (options && options.quoteIdent) {
66
- this._quoteIdent = options.quoteIdent;
67
- }
68
- else {
69
- this._quoteIdent = (identifier) => {
70
- if (typeof identifier !== "string") {
71
- throw new Error(SqlBuilderError.QUOTE_IDENT_NEED_STRING(identifier));
72
- }
73
- const trimmed = identifier.trim();
74
- if (!trimmed) {
75
- throw new Error(SqlBuilderError.IDENT_EMPTY);
76
- }
77
- // 默认行为(MySQL 风格):允许特殊字符,但对反引号进行转义
78
- const escaped = trimmed.replace(/`/g, "``");
79
- return `\`${escaped}\``;
80
- };
81
- }
82
- }
83
- _isQuotedIdent(value) {
84
- return SqlCheck.isQuotedIdentPaired(value);
85
- }
86
- _startsWithQuote(value) {
87
- return SqlCheck.startsWithQuote(value);
88
- }
89
- /**
90
- * 重置构建器状态
91
- */
92
- reset() {
93
- this._select = [];
94
- this._from = "";
95
- this._where = [];
96
- this._joins = [];
97
- this._orderBy = [];
98
- this._groupBy = [];
99
- this._having = [];
100
- this._limit = null;
101
- this._offset = null;
102
- this._params = [];
103
- return this;
104
- }
105
- /**
106
- * 转义字段名
107
- */
108
- _escapeField(field) {
109
- if (typeof field !== "string") {
110
- return field;
111
- }
112
- field = field.trim();
113
- // 防止不完整引用被误认为“已安全引用”
114
- SqlCheck.assertPairedQuotedIdentIfStartsWithQuote(field, "字段标识符");
115
- // 如果是 * 或已经被引用,直接返回
116
- if (field === "*" || this._isQuotedIdent(field)) {
117
- return field;
118
- }
119
- try {
120
- SqlCheck.assertNoExprField(field);
121
- }
122
- catch {
123
- // 保持 SqlBuilder 报错文案统一
124
- throw new Error(SqlBuilderError.FIELD_EXPR_NOT_ALLOWED(field));
125
- }
126
- // 处理别名(AS关键字)
127
- if (field.toUpperCase().includes(" AS ")) {
128
- const parts = field.split(/\s+AS\s+/i);
129
- if (parts.length !== 2) {
130
- throw new Error(SqlBuilderError.FIELD_INVALID(field));
131
- }
132
- const fieldPart = parts[0];
133
- const aliasPart = parts[1];
134
- if (typeof fieldPart !== "string" || typeof aliasPart !== "string") {
135
- throw new Error(SqlBuilderError.FIELD_INVALID(field));
136
- }
137
- const cleanFieldPart = fieldPart.trim();
138
- const cleanAliasPart = aliasPart.trim();
139
- // alias 仅允许安全标识符或已被引用
140
- SqlCheck.assertSafeAlias(cleanAliasPart);
141
- return `${this._escapeField(cleanFieldPart)} AS ${cleanAliasPart}`;
142
- }
143
- // 处理表名.字段名的情况(多表联查)
144
- if (field.includes(".")) {
145
- const parts = field.split(".");
146
- return parts
147
- .map((part) => {
148
- part = part.trim();
149
- if (part === "*" || this._isQuotedIdent(part)) {
150
- return part;
151
- }
152
- SqlCheck.assertPairedQuotedIdentIfStartsWithQuote(part, "字段标识符");
153
- return this._quoteIdent(part);
154
- })
155
- .join(".");
156
- }
157
- // 处理单个字段名
158
- return this._quoteIdent(field);
159
- }
160
- _validateIdentifierPart(part, kind) {
161
- SqlCheck.assertSafeIdentifierPart(part, kind);
162
- }
163
- /**
164
- * 转义表名
165
- */
166
- _escapeTable(table) {
167
- if (typeof table !== "string") {
168
- return table;
169
- }
170
- table = table.trim();
171
- // 防止不完整引用被误认为“已安全引用”
172
- if (this._startsWithQuote(table) && !this._isQuotedIdent(table)) {
173
- // 注意:这里可能是 `table` alias 的形式,整体不成对,但 namePart 可能成对。
174
- // 因此这里只做“整体是单段引用”的判断,具体在后续 namePart 分支里校验。
175
- }
176
- if (this._isQuotedIdent(table)) {
177
- return table;
178
- }
179
- const parts = table.split(/\s+/).filter((p) => p.length > 0);
180
- if (parts.length === 0) {
181
- throw new Error(SqlBuilderError.FROM_EMPTY);
182
- }
183
- if (parts.length > 2) {
184
- throw new Error(SqlBuilderError.TABLE_REF_TOO_MANY_PARTS(table));
185
- }
186
- const namePartRaw = parts[0];
187
- if (typeof namePartRaw !== "string" || namePartRaw.trim() === "") {
188
- throw new Error(SqlBuilderError.FROM_EMPTY);
189
- }
190
- const namePart = namePartRaw.trim();
191
- const aliasPartRaw = parts.length === 2 ? parts[1] : null;
192
- const aliasPart = typeof aliasPartRaw === "string" ? aliasPartRaw.trim() : null;
193
- const nameSegments = namePart.split(".");
194
- if (nameSegments.length > 2) {
195
- throw new Error(SqlBuilderError.TABLE_REF_SCHEMA_TOO_DEEP(table));
196
- }
197
- let escapedName = "";
198
- if (nameSegments.length === 2) {
199
- const schemaRaw = nameSegments[0];
200
- const tableNameRaw = nameSegments[1];
201
- if (typeof schemaRaw !== "string" || typeof tableNameRaw !== "string") {
202
- throw new Error(SqlBuilderError.TABLE_REF_SCHEMA_TOO_DEEP(table));
203
- }
204
- const schema = schemaRaw.trim();
205
- const tableName = tableNameRaw.trim();
206
- const escapedSchema = this._isQuotedIdent(schema)
207
- ? schema
208
- : (() => {
209
- if (this._startsWithQuote(schema) && !this._isQuotedIdent(schema)) {
210
- throw new Error(SqlBuilderError.SCHEMA_QUOTE_NOT_PAIRED(schema));
211
- }
212
- this._validateIdentifierPart(schema, "schema");
213
- return this._quoteIdent(schema);
214
- })();
215
- const escapedTableName = this._isQuotedIdent(tableName)
216
- ? tableName
217
- : (() => {
218
- if (this._startsWithQuote(tableName) && !this._isQuotedIdent(tableName)) {
219
- throw new Error(SqlBuilderError.TABLE_QUOTE_NOT_PAIRED(tableName));
220
- }
221
- this._validateIdentifierPart(tableName, "table");
222
- return this._quoteIdent(tableName);
223
- })();
224
- escapedName = `${escapedSchema}.${escapedTableName}`;
225
- }
226
- else {
227
- const tableNameRaw = nameSegments[0];
228
- if (typeof tableNameRaw !== "string") {
229
- throw new Error(SqlBuilderError.FROM_EMPTY);
230
- }
231
- const tableName = tableNameRaw.trim();
232
- if (this._isQuotedIdent(tableName)) {
233
- escapedName = tableName;
234
- }
235
- else {
236
- if (this._startsWithQuote(tableName) && !this._isQuotedIdent(tableName)) {
237
- throw new Error(SqlBuilderError.TABLE_QUOTE_NOT_PAIRED(tableName));
238
- }
239
- this._validateIdentifierPart(tableName, "table");
240
- escapedName = this._quoteIdent(tableName);
241
- }
242
- }
243
- if (aliasPart) {
244
- this._validateIdentifierPart(aliasPart, "alias");
245
- return `${escapedName} ${aliasPart}`;
246
- }
247
- return escapedName;
248
- }
249
- /**
250
- * 验证参数
251
- */
252
- _validateParam(value) {
253
- SqlCheck.assertNoUndefinedParam(value, "SQL 参数值");
254
- }
255
- /**
256
- * 处理单个操作符条件
257
- */
258
- _applyOperator(fieldName, operator, value) {
259
- const escapedField = this._escapeField(fieldName);
260
- switch (operator) {
261
- case "$ne":
262
- case "$not":
263
- this._validateParam(value);
264
- this._where.push(`${escapedField} != ?`);
265
- this._params.push(value);
266
- break;
267
- case "$in":
268
- if (!Array.isArray(value)) {
269
- throw new Error(`$in 操作符的值必须是数组 (operator: ${operator})`);
270
- }
271
- if (value.length === 0) {
272
- throw new Error(`$in 操作符的数组不能为空。提示:空数组会导致查询永远不匹配任何记录,这通常不是预期行为。请检查查询条件或移除该字段。`);
273
- }
274
- const placeholders = value.map(() => "?").join(",");
275
- this._where.push(`${escapedField} IN (${placeholders})`);
276
- this._params.push(...value);
277
- break;
278
- case "$nin":
279
- case "$notIn":
280
- if (!Array.isArray(value)) {
281
- throw new Error(`$nin/$notIn 操作符的值必须是数组 (operator: ${operator})`);
282
- }
283
- if (value.length === 0) {
284
- throw new Error(`$nin/$notIn 操作符的数组不能为空。提示:空数组会导致查询匹配所有记录,这通常不是预期行为。请检查查询条件或移除该字段。`);
285
- }
286
- const placeholders2 = value.map(() => "?").join(",");
287
- this._where.push(`${escapedField} NOT IN (${placeholders2})`);
288
- this._params.push(...value);
289
- break;
290
- case "$like":
291
- this._validateParam(value);
292
- this._where.push(`${escapedField} LIKE ?`);
293
- this._params.push(value);
294
- break;
295
- case "$notLike":
296
- this._validateParam(value);
297
- this._where.push(`${escapedField} NOT LIKE ?`);
298
- this._params.push(value);
299
- break;
300
- case "$gt":
301
- this._validateParam(value);
302
- this._where.push(`${escapedField} > ?`);
303
- this._params.push(value);
304
- break;
305
- case "$gte":
306
- this._validateParam(value);
307
- this._where.push(`${escapedField} >= ?`);
308
- this._params.push(value);
309
- break;
310
- case "$lt":
311
- this._validateParam(value);
312
- this._where.push(`${escapedField} < ?`);
313
- this._params.push(value);
314
- break;
315
- case "$lte":
316
- this._validateParam(value);
317
- this._where.push(`${escapedField} <= ?`);
318
- this._params.push(value);
319
- break;
320
- case "$between":
321
- if (Array.isArray(value) && value.length === 2) {
322
- this._validateParam(value[0]);
323
- this._validateParam(value[1]);
324
- this._where.push(`${escapedField} BETWEEN ? AND ?`);
325
- this._params.push(value[0], value[1]);
326
- }
327
- break;
328
- case "$notBetween":
329
- if (Array.isArray(value) && value.length === 2) {
330
- this._validateParam(value[0]);
331
- this._validateParam(value[1]);
332
- this._where.push(`${escapedField} NOT BETWEEN ? AND ?`);
333
- this._params.push(value[0], value[1]);
334
- }
335
- break;
336
- case "$null":
337
- if (value === true) {
338
- this._where.push(`${escapedField} IS NULL`);
339
- }
340
- break;
341
- case "$notNull":
342
- if (value === true) {
343
- this._where.push(`${escapedField} IS NOT NULL`);
344
- }
345
- break;
346
- default:
347
- // 等于条件
348
- this._validateParam(value);
349
- this._where.push(`${escapedField} = ?`);
350
- this._params.push(value);
351
- }
352
- }
353
- /**
354
- * 处理复杂的 WHERE 条件对象
355
- */
356
- _processWhereConditions(whereObj) {
357
- if (!whereObj || typeof whereObj !== "object") {
358
- return;
359
- }
360
- Object.entries(whereObj).forEach(([key, value]) => {
361
- // 跳过undefined值
362
- if (value === undefined) {
363
- return;
364
- }
365
- if (key === "$and") {
366
- if (Array.isArray(value)) {
367
- value.forEach((condition) => {
368
- if (condition && typeof condition === "object" && !Array.isArray(condition)) {
369
- this._processWhereConditions(condition);
370
- }
371
- });
372
- }
373
- }
374
- else if (key === "$or") {
375
- if (Array.isArray(value)) {
376
- const orConditions = [];
377
- const tempParams = [];
378
- value.forEach((condition) => {
379
- if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
380
- return;
381
- }
382
- const tempBuilder = new SqlBuilder({ quoteIdent: this._quoteIdent });
383
- tempBuilder._processWhereConditions(condition);
384
- if (tempBuilder._where.length > 0) {
385
- orConditions.push(`(${tempBuilder._where.join(" AND ")})`);
386
- tempParams.push(...tempBuilder._params);
387
- }
388
- });
389
- if (orConditions.length > 0) {
390
- this._where.push(`(${orConditions.join(" OR ")})`);
391
- this._params.push(...tempParams);
392
- }
393
- }
394
- }
395
- else if (key.includes("$")) {
396
- // 一级属性格式:age$gt, role$in 等
397
- const lastDollarIndex = key.lastIndexOf("$");
398
- const fieldName = key.substring(0, lastDollarIndex);
399
- const operator = ("$" + key.substring(lastDollarIndex + 1));
400
- this._applyOperator(fieldName, operator, value);
401
- }
402
- else {
403
- // 检查值是否为对象(嵌套条件)
404
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
405
- // 嵌套条件:如 { age: { $gt: 18 } }
406
- for (const [op, val] of Object.entries(value)) {
407
- this._applyOperator(key, op, val);
408
- }
409
- }
410
- else {
411
- // 简单的等于条件
412
- this._validateParam(value);
413
- const escapedKey = this._escapeField(key);
414
- this._where.push(`${escapedKey} = ?`);
415
- this._params.push(value);
416
- }
417
- }
418
- });
419
- }
420
- /**
421
- * 获取 WHERE 条件(供 DbHelper 使用)
422
- */
423
- getWhereConditions() {
424
- return {
425
- sql: this._where.length > 0 ? this._where.join(" AND ") : "",
426
- params: [...this._params]
427
- };
428
- }
429
- /**
430
- * SELECT 字段
431
- */
432
- select(fields = "*") {
433
- if (Array.isArray(fields)) {
434
- this._select = [...this._select, ...fields.map((field) => this._escapeField(field))];
435
- }
436
- else if (typeof fields === "string") {
437
- this._select.push(this._escapeField(fields));
438
- }
439
- else {
440
- throw new Error(SqlBuilderError.SELECT_FIELDS_INVALID);
441
- }
442
- return this;
443
- }
444
- /**
445
- * SELECT 原始表达式(不做转义)
446
- */
447
- selectRaw(expr) {
448
- if (typeof expr !== "string" || !expr.trim()) {
449
- throw new Error(SqlBuilderError.SELECT_RAW_NEED_NON_EMPTY(expr));
450
- }
451
- this._select.push(expr);
452
- return this;
453
- }
454
- /**
455
- * FROM 表名
456
- */
457
- from(table) {
458
- if (typeof table !== "string" || !table.trim()) {
459
- throw new Error(SqlBuilderError.FROM_NEED_NON_EMPTY(table));
460
- }
461
- this._from = this._escapeTable(table.trim());
462
- return this;
463
- }
464
- /**
465
- * FROM 原始表达式(不做转义)
466
- */
467
- fromRaw(tableExpr) {
468
- if (typeof tableExpr !== "string" || !tableExpr.trim()) {
469
- throw new Error(SqlBuilderError.FROM_RAW_NEED_NON_EMPTY(tableExpr));
470
- }
471
- this._from = tableExpr;
472
- return this;
473
- }
474
- where(conditionOrField, value) {
475
- if (typeof conditionOrField === "object" && conditionOrField !== null) {
476
- this._processWhereConditions(conditionOrField);
477
- return this;
478
- }
479
- if (typeof conditionOrField === "string") {
480
- if (value === undefined) {
481
- throw new Error(SqlBuilderError.WHERE_VALUE_REQUIRED);
482
- }
483
- this._validateParam(value);
484
- const escapedCondition = this._escapeField(conditionOrField);
485
- this._where.push(`${escapedCondition} = ?`);
486
- this._params.push(value);
487
- return this;
488
- }
489
- return this;
490
- }
491
- /**
492
- * WHERE 原始片段(不做转义),可附带参数。
493
- */
494
- whereRaw(sql, params) {
495
- if (typeof sql !== "string" || !sql.trim()) {
496
- throw new Error(SqlBuilderError.WHERE_RAW_NEED_NON_EMPTY(sql));
497
- }
498
- this._where.push(sql);
499
- if (params && params.length > 0) {
500
- this._params.push(...params);
501
- }
502
- return this;
503
- }
504
- /**
505
- * LEFT JOIN
506
- */
507
- leftJoin(table, on) {
508
- if (typeof table !== "string" || typeof on !== "string") {
509
- throw new Error(SqlBuilderError.JOIN_NEED_STRING(table, on));
510
- }
511
- const escapedTable = this._escapeTable(table);
512
- this._joins.push(`LEFT JOIN ${escapedTable} ON ${on}`);
513
- return this;
514
- }
515
- /**
516
- * RIGHT JOIN
517
- */
518
- rightJoin(table, on) {
519
- if (typeof table !== "string" || typeof on !== "string") {
520
- throw new Error(SqlBuilderError.JOIN_NEED_STRING(table, on));
521
- }
522
- const escapedTable = this._escapeTable(table);
523
- this._joins.push(`RIGHT JOIN ${escapedTable} ON ${on}`);
524
- return this;
525
- }
526
- /**
527
- * INNER JOIN
528
- */
529
- innerJoin(table, on) {
530
- if (typeof table !== "string" || typeof on !== "string") {
531
- throw new Error(SqlBuilderError.JOIN_NEED_STRING(table, on));
532
- }
533
- const escapedTable = this._escapeTable(table);
534
- this._joins.push(`INNER JOIN ${escapedTable} ON ${on}`);
535
- return this;
536
- }
537
- /**
538
- * ORDER BY
539
- * @param fields - 格式为 ["field#ASC", "field2#DESC"]
540
- */
541
- orderBy(fields) {
542
- if (!Array.isArray(fields)) {
543
- throw new Error(SqlBuilderError.ORDER_BY_NEED_ARRAY);
544
- }
545
- fields.forEach((item) => {
546
- if (typeof item !== "string" || !item.includes("#")) {
547
- throw new Error(SqlBuilderError.ORDER_BY_ITEM_NEED_HASH(item));
548
- }
549
- const parts = item.split("#");
550
- if (parts.length !== 2) {
551
- throw new Error(SqlBuilderError.ORDER_BY_ITEM_NEED_HASH(item));
552
- }
553
- const fieldName = parts[0];
554
- const direction = parts[1];
555
- if (typeof fieldName !== "string" || typeof direction !== "string") {
556
- throw new Error(SqlBuilderError.ORDER_BY_ITEM_NEED_HASH(item));
557
- }
558
- const cleanField = fieldName.trim();
559
- const cleanDir = direction.trim().toUpperCase();
560
- if (!cleanField) {
561
- throw new Error(SqlBuilderError.ORDER_BY_FIELD_EMPTY(item));
562
- }
563
- if (!["ASC", "DESC"].includes(cleanDir)) {
564
- throw new Error(SqlBuilderError.ORDER_BY_DIR_INVALID(cleanDir));
565
- }
566
- const escapedField = this._escapeField(cleanField);
567
- this._orderBy.push(`${escapedField} ${cleanDir}`);
568
- });
569
- return this;
570
- }
571
- /**
572
- * GROUP BY
573
- */
574
- groupBy(field) {
575
- if (Array.isArray(field)) {
576
- const escapedFields = field.filter((f) => typeof f === "string").map((f) => this._escapeField(f));
577
- this._groupBy = [...this._groupBy, ...escapedFields];
578
- }
579
- else if (typeof field === "string") {
580
- this._groupBy.push(this._escapeField(field));
581
- }
582
- return this;
583
- }
584
- /**
585
- * HAVING
586
- */
587
- having(condition) {
588
- if (typeof condition === "string") {
589
- this._having.push(condition);
590
- }
591
- return this;
592
- }
593
- /**
594
- * LIMIT
595
- */
596
- limit(count, offset) {
597
- if (typeof count !== "number" || count < 0) {
598
- throw new Error(SqlBuilderError.LIMIT_MUST_NON_NEGATIVE(count));
599
- }
600
- this._limit = Math.floor(count);
601
- if (offset !== undefined && offset !== null) {
602
- if (typeof offset !== "number" || offset < 0) {
603
- throw new Error(SqlBuilderError.OFFSET_MUST_NON_NEGATIVE(offset));
604
- }
605
- this._offset = Math.floor(offset);
606
- }
607
- return this;
608
- }
609
- /**
610
- * OFFSET
611
- */
612
- offset(count) {
613
- if (typeof count !== "number" || count < 0) {
614
- throw new Error(SqlBuilderError.OFFSET_COUNT_MUST_NON_NEGATIVE(count));
615
- }
616
- this._offset = Math.floor(count);
617
- return this;
618
- }
619
- /**
620
- * 构建 SELECT 查询
621
- */
622
- toSelectSql() {
623
- let sql = "SELECT ";
624
- sql += this._select.length > 0 ? this._select.join(", ") : "*";
625
- if (!this._from) {
626
- throw new Error(SqlBuilderError.FROM_REQUIRED);
627
- }
628
- sql += ` FROM ${this._from}`;
629
- if (this._joins.length > 0) {
630
- sql += " " + this._joins.join(" ");
631
- }
632
- if (this._where.length > 0) {
633
- sql += " WHERE " + this._where.join(" AND ");
634
- }
635
- if (this._groupBy.length > 0) {
636
- sql += " GROUP BY " + this._groupBy.join(", ");
637
- }
638
- if (this._having.length > 0) {
639
- sql += " HAVING " + this._having.join(" AND ");
640
- }
641
- if (this._orderBy.length > 0) {
642
- sql += " ORDER BY " + this._orderBy.join(", ");
643
- }
644
- if (this._limit !== null) {
645
- sql += ` LIMIT ${this._limit}`;
646
- if (this._offset !== null) {
647
- sql += ` OFFSET ${this._offset}`;
648
- }
649
- }
650
- return { sql, params: [...this._params] };
651
- }
652
- /**
653
- * 构建 INSERT 查询
654
- */
655
- toInsertSql(table, data) {
656
- if (!table || typeof table !== "string") {
657
- throw new Error(SqlBuilderError.INSERT_NEED_TABLE(table));
658
- }
659
- if (!data || typeof data !== "object") {
660
- throw new Error(SqlBuilderError.INSERT_NEED_DATA(table, data));
661
- }
662
- const escapedTable = this._escapeTable(table);
663
- if (Array.isArray(data)) {
664
- const fields = SqlCheck.assertBatchInsertRowsConsistent(data, { table: table });
665
- const escapedFields = fields.map((field) => this._escapeField(field));
666
- const placeholders = fields.map(() => "?").join(", ");
667
- const values = data.map(() => `(${placeholders})`).join(", ");
668
- const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES ${values}`;
669
- const params = [];
670
- for (let i = 0; i < data.length; i++) {
671
- const row = data[i];
672
- for (const field of fields) {
673
- const value = row[field];
674
- this._validateParam(value);
675
- params.push(value);
676
- }
677
- }
678
- return { sql, params };
679
- }
680
- else {
681
- const fields = Object.keys(data);
682
- if (fields.length === 0) {
683
- throw new Error(SqlBuilderError.INSERT_NEED_AT_LEAST_ONE_FIELD(table));
684
- }
685
- for (const field of fields) {
686
- const record = data;
687
- this._validateParam(record[field]);
688
- }
689
- const escapedFields = fields.map((field) => this._escapeField(field));
690
- const placeholders = fields.map(() => "?").join(", ");
691
- const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`;
692
- const params = [];
693
- for (const field of fields) {
694
- const value = data[field];
695
- this._validateParam(value);
696
- params.push(value);
697
- }
698
- return { sql, params };
699
- }
700
- }
701
- /**
702
- * 构建 UPDATE 查询
703
- */
704
- toUpdateSql(table, data) {
705
- if (!table || typeof table !== "string") {
706
- throw new Error(SqlBuilderError.UPDATE_NEED_TABLE);
707
- }
708
- if (!data || typeof data !== "object" || Array.isArray(data)) {
709
- throw new Error(SqlBuilderError.UPDATE_NEED_OBJECT);
710
- }
711
- const fields = Object.keys(data);
712
- if (fields.length === 0) {
713
- throw new Error(SqlBuilderError.UPDATE_NEED_AT_LEAST_ONE_FIELD);
714
- }
715
- const escapedTable = this._escapeTable(table);
716
- const setFields = fields.map((field) => `${this._escapeField(field)} = ?`);
717
- const params = [...Object.values(data), ...this._params];
718
- let sql = `UPDATE ${escapedTable} SET ${setFields.join(", ")}`;
719
- if (this._where.length > 0) {
720
- sql += " WHERE " + this._where.join(" AND ");
721
- }
722
- else {
723
- throw new Error(SqlBuilderError.UPDATE_NEED_WHERE);
724
- }
725
- return { sql, params };
726
- }
727
- /**
728
- * 构建 DELETE 查询
729
- */
730
- toDeleteSql(table) {
731
- if (!table || typeof table !== "string") {
732
- throw new Error(SqlBuilderError.DELETE_NEED_TABLE);
733
- }
734
- const escapedTable = this._escapeTable(table);
735
- let sql = `DELETE FROM ${escapedTable}`;
736
- if (this._where.length > 0) {
737
- sql += " WHERE " + this._where.join(" AND ");
738
- }
739
- else {
740
- throw new Error(SqlBuilderError.DELETE_NEED_WHERE);
741
- }
742
- return { sql, params: [...this._params] };
743
- }
744
- /**
745
- * 构建 COUNT 查询
746
- */
747
- toCountSql() {
748
- let sql = "SELECT COUNT(*) as total";
749
- if (!this._from) {
750
- throw new Error(SqlBuilderError.COUNT_NEED_FROM);
751
- }
752
- sql += ` FROM ${this._from}`;
753
- if (this._joins.length > 0) {
754
- sql += " " + this._joins.join(" ");
755
- }
756
- if (this._where.length > 0) {
757
- sql += " WHERE " + this._where.join(" AND ");
758
- }
759
- return { sql, params: [...this._params] };
760
- }
761
- static toDeleteInSql(options) {
762
- if (typeof options.table !== "string" || !options.table.trim()) {
763
- throw new Error(SqlBuilderError.TO_DELETE_IN_NEED_TABLE(options.table));
764
- }
765
- if (typeof options.idField !== "string" || !options.idField.trim()) {
766
- throw new Error(SqlBuilderError.TO_DELETE_IN_NEED_ID_FIELD(options.idField));
767
- }
768
- if (!Array.isArray(options.ids)) {
769
- throw new Error(SqlBuilderError.TO_DELETE_IN_NEED_IDS);
770
- }
771
- if (options.ids.length === 0) {
772
- return { sql: "", params: [] };
773
- }
774
- const placeholders = options.ids.map(() => "?").join(",");
775
- const sql = `DELETE FROM ${options.quoteIdent(options.table)} WHERE ${options.quoteIdent(options.idField)} IN (${placeholders})`;
776
- return { sql: sql, params: [...options.ids] };
777
- }
778
- static toUpdateCaseByIdSql(options) {
779
- if (typeof options.table !== "string" || !options.table.trim()) {
780
- throw new Error(SqlBuilderError.TO_UPDATE_CASE_NEED_TABLE(options.table));
781
- }
782
- if (typeof options.idField !== "string" || !options.idField.trim()) {
783
- throw new Error(SqlBuilderError.TO_UPDATE_CASE_NEED_ID_FIELD(options.idField));
784
- }
785
- if (!Array.isArray(options.rows)) {
786
- throw new Error(SqlBuilderError.TO_UPDATE_CASE_NEED_ROWS);
787
- }
788
- if (options.rows.length === 0) {
789
- return { sql: "", params: [] };
790
- }
791
- if (!Array.isArray(options.fields)) {
792
- throw new Error(SqlBuilderError.TO_UPDATE_CASE_NEED_FIELDS);
793
- }
794
- if (options.fields.length === 0) {
795
- return { sql: "", params: [] };
796
- }
797
- const ids = options.rows.map((r) => r.id);
798
- const placeholders = ids.map(() => "?").join(",");
799
- const setSqlList = [];
800
- const args = [];
801
- const quotedId = options.quoteIdent(options.idField);
802
- for (const field of options.fields) {
803
- const whenList = [];
804
- for (const row of options.rows) {
805
- if (!(field in row.data)) {
806
- continue;
807
- }
808
- whenList.push("WHEN ? THEN ?");
809
- args.push(row.id);
810
- const value = row.data[field];
811
- SqlCheck.assertNoUndefinedParam(value, "SQL 参数值");
812
- args.push(value);
813
- }
814
- if (whenList.length === 0) {
815
- continue;
816
- }
817
- const quotedField = options.quoteIdent(field);
818
- setSqlList.push(`${quotedField} = CASE ${quotedId} ${whenList.join(" ")} ELSE ${quotedField} END`);
819
- }
820
- setSqlList.push(`${options.quoteIdent(options.updatedAtField)} = ?`);
821
- args.push(options.updatedAtValue);
822
- for (const id of ids) {
823
- args.push(id);
824
- }
825
- let sql = `UPDATE ${options.quoteIdent(options.table)} SET ${setSqlList.join(", ")} WHERE ${quotedId} IN (${placeholders})`;
826
- if (options.stateGtZero && options.stateField) {
827
- sql += ` AND ${options.quoteIdent(options.stateField)} > 0`;
828
- }
829
- return { sql: sql, params: args };
830
- }
831
- }
832
- /**
833
- * 创建新的 SQL 构建器实例
834
- */
835
- export function createQueryBuilder() {
836
- return new SqlBuilder();
837
- }