befly 3.20.8 → 3.20.9

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.
@@ -1,4 +1,3 @@
1
- import { SqlErrors } from "./errors.js";
2
1
  import { validateParam } from "./util.js";
3
2
  import { isNonEmptyString, isString } from "../../utils/is.js";
4
3
 
@@ -19,7 +18,7 @@ export function appendSelectItems(list, fields) {
19
18
  return;
20
19
  }
21
20
 
22
- throw new Error(SqlErrors.SELECT_FIELDS_INVALID, {
21
+ throw new Error("SELECT 字段必须是字符串或数组", {
23
22
  cause: null,
24
23
  code: "validation"
25
24
  });
@@ -27,7 +26,7 @@ export function appendSelectItems(list, fields) {
27
26
 
28
27
  export function appendSelectRaw(list, expr) {
29
28
  if (!isNonEmptyString(expr)) {
30
- throw new Error(SqlErrors.SELECT_RAW_NEED_NON_EMPTY(expr), {
29
+ throw new Error(`selectRaw 需要非空字符串 (expr: ${String(expr)})`, {
31
30
  cause: null,
32
31
  code: "validation"
33
32
  });
@@ -37,7 +36,7 @@ export function appendSelectRaw(list, expr) {
37
36
 
38
37
  export function setFromValue(state, table, isRaw) {
39
38
  if (!isNonEmptyString(table)) {
40
- const err = isRaw ? SqlErrors.FROM_RAW_NEED_NON_EMPTY(table) : SqlErrors.FROM_NEED_NON_EMPTY(table);
39
+ const err = isRaw ? `fromRaw 需要非空字符串 (tableExpr: ${String(table)})` : `FROM 表名必须是非空字符串 (table: ${String(table)})`;
41
40
  throw new Error(err, {
42
41
  cause: null,
43
42
  code: "validation"
@@ -49,7 +48,7 @@ export function setFromValue(state, table, isRaw) {
49
48
 
50
49
  export function appendJoinItem(list, joinType, table, on) {
51
50
  if (!isString(table) || !isString(on)) {
52
- throw new Error(SqlErrors.JOIN_NEED_STRING(table, on), {
51
+ throw new Error(`JOIN 表名和条件必须是字符串 (table: ${String(table)}, on: ${String(on)})`, {
53
52
  cause: null,
54
53
  code: "validation"
55
54
  });
@@ -60,7 +59,7 @@ export function appendJoinItem(list, joinType, table, on) {
60
59
 
61
60
  export function appendOrderByItems(list, fields) {
62
61
  if (!Array.isArray(fields)) {
63
- throw new Error(SqlErrors.ORDER_BY_NEED_ARRAY, {
62
+ throw new Error('orderBy 必须是字符串数组,格式为 "字段#方向"', {
64
63
  cause: null,
65
64
  code: "validation"
66
65
  });
@@ -68,7 +67,7 @@ export function appendOrderByItems(list, fields) {
68
67
 
69
68
  for (const item of fields) {
70
69
  if (!isString(item) || !item.includes("#")) {
71
- throw new Error(SqlErrors.ORDER_BY_ITEM_NEED_HASH(item), {
70
+ throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
72
71
  cause: null,
73
72
  code: "validation"
74
73
  });
@@ -76,7 +75,7 @@ export function appendOrderByItems(list, fields) {
76
75
 
77
76
  const parts = item.split("#");
78
77
  if (parts.length !== 2) {
79
- throw new Error(SqlErrors.ORDER_BY_ITEM_NEED_HASH(item), {
78
+ throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
80
79
  cause: null,
81
80
  code: "validation"
82
81
  });
@@ -88,14 +87,14 @@ export function appendOrderByItems(list, fields) {
88
87
  const cleanDir = direction.trim().toUpperCase();
89
88
 
90
89
  if (!cleanField) {
91
- throw new Error(SqlErrors.ORDER_BY_FIELD_EMPTY(item), {
90
+ throw new Error(`orderBy 中字段名不能为空 (item: ${item})`, {
92
91
  cause: null,
93
92
  code: "validation"
94
93
  });
95
94
  }
96
95
 
97
96
  if (!["ASC", "DESC"].includes(cleanDir)) {
98
- throw new Error(SqlErrors.ORDER_BY_DIR_INVALID(cleanDir), {
97
+ throw new Error(`ORDER BY 方向必须是 ASC 或 DESC (direction: ${cleanDir})`, {
99
98
  cause: null,
100
99
  code: "validation"
101
100
  });
@@ -107,30 +106,24 @@ export function appendOrderByItems(list, fields) {
107
106
 
108
107
  export function appendWhereInput(root, conditionOrField, value) {
109
108
  if (conditionOrField && typeof conditionOrField === "object" && !Array.isArray(conditionOrField)) {
110
- const node = parseWhereObject(conditionOrField);
111
- if (node && node.items.length > 0) {
112
- root.items.push(node);
113
- }
109
+ appendWhereObject(root, conditionOrField);
114
110
  return;
115
111
  }
116
112
 
117
113
  if (isString(conditionOrField)) {
118
114
  if (value === undefined) {
119
- throw new Error(SqlErrors.WHERE_VALUE_REQUIRED, {
115
+ throw new Error("where(field, value) 不允许省略 value。若需传入原始 WHERE,请使用 whereRaw", {
120
116
  cause: null,
121
117
  code: "validation"
122
118
  });
123
119
  }
124
- const node = buildOperatorNode(conditionOrField, "=", value);
125
- if (node) {
126
- root.items.push(node);
127
- }
120
+ appendWhereNode(root, buildOperatorNode(conditionOrField, "=", value));
128
121
  }
129
122
  }
130
123
 
131
124
  export function appendWhereRaw(root, sql, params) {
132
125
  if (!isNonEmptyString(sql)) {
133
- throw new Error(SqlErrors.WHERE_RAW_NEED_NON_EMPTY(sql), {
126
+ throw new Error(`whereRaw 需要非空字符串 (sql: ${String(sql)})`, {
134
127
  cause: null,
135
128
  code: "validation"
136
129
  });
@@ -144,6 +137,19 @@ export function appendWhereRaw(root, sql, params) {
144
137
  root.items.push({ type: "raw", sql: sql, params: paramList });
145
138
  }
146
139
 
140
+ function appendWhereNode(root, node) {
141
+ if (node) {
142
+ root.items.push(node);
143
+ }
144
+ }
145
+
146
+ function appendWhereObject(root, whereObj) {
147
+ const node = parseWhereObject(whereObj);
148
+ if (node.items.length > 0) {
149
+ root.items.push(node);
150
+ }
151
+ }
152
+
147
153
  function parseWhereObject(whereObj) {
148
154
  if (!whereObj || typeof whereObj !== "object") {
149
155
  return { type: "group", join: "AND", items: [] };
@@ -157,117 +163,130 @@ function parseWhereObject(whereObj) {
157
163
  }
158
164
 
159
165
  if (key === "$and") {
160
- if (Array.isArray(value)) {
161
- for (const condition of value) {
162
- if (condition && typeof condition === "object" && !Array.isArray(condition)) {
163
- const sub = parseWhereObject(condition);
164
- if (sub.items.length > 0) {
165
- if (sub.join === "AND") {
166
- for (const item of sub.items) {
167
- group.items.push(item);
168
- }
169
- } else {
170
- group.items.push(sub);
171
- }
172
- }
173
- }
174
- }
175
- }
166
+ appendAndConditions(group, value);
176
167
  continue;
177
168
  }
178
169
 
179
170
  if (key === "$or") {
180
- if (Array.isArray(value)) {
181
- const orGroup = { type: "group", join: "OR", items: [] };
182
- for (const condition of value) {
183
- if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
184
- continue;
185
- }
186
- const sub = parseWhereObject(condition);
187
- if (sub.items.length > 0) {
188
- orGroup.items.push(sub);
189
- }
190
- }
191
- if (orGroup.items.length > 0) {
192
- group.items.push(orGroup);
193
- }
194
- }
171
+ appendOrConditions(group, value);
195
172
  continue;
196
173
  }
197
174
 
198
- if (key.includes("$")) {
199
- const lastDollarIndex = key.lastIndexOf("$");
200
- const fieldName = key.substring(0, lastDollarIndex);
201
- const operator = "$" + key.substring(lastDollarIndex + 1);
202
- const node = buildOperatorNode(fieldName, operator, value);
203
- if (node) {
204
- group.items.push(node);
205
- }
175
+ appendFieldCondition(group, key, value);
176
+ }
177
+
178
+ return group;
179
+ }
180
+
181
+ function appendAndConditions(group, value) {
182
+ if (!Array.isArray(value)) {
183
+ return;
184
+ }
185
+
186
+ for (const condition of value) {
187
+ if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
206
188
  continue;
207
189
  }
208
190
 
209
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
210
- for (const [op, val] of Object.entries(value)) {
211
- const node = buildOperatorNode(key, op, val);
212
- if (node) {
213
- group.items.push(node);
214
- }
191
+ const sub = parseWhereObject(condition);
192
+ if (sub.items.length === 0) {
193
+ continue;
194
+ }
195
+
196
+ if (sub.join === "AND") {
197
+ for (const item of sub.items) {
198
+ group.items.push(item);
215
199
  }
216
200
  continue;
217
201
  }
218
202
 
219
- const node = buildOperatorNode(key, "=", value);
220
- if (node) {
221
- group.items.push(node);
203
+ group.items.push(sub);
204
+ }
205
+ }
206
+
207
+ function appendOrConditions(group, value) {
208
+ if (!Array.isArray(value)) {
209
+ return;
210
+ }
211
+
212
+ const orGroup = { type: "group", join: "OR", items: [] };
213
+ for (const condition of value) {
214
+ if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
215
+ continue;
216
+ }
217
+
218
+ const sub = parseWhereObject(condition);
219
+ if (sub.items.length > 0) {
220
+ orGroup.items.push(sub);
222
221
  }
223
222
  }
224
223
 
225
- return group;
224
+ if (orGroup.items.length > 0) {
225
+ group.items.push(orGroup);
226
+ }
227
+ }
228
+
229
+ function appendFieldCondition(group, key, value) {
230
+ if (key.includes("$")) {
231
+ const lastDollarIndex = key.lastIndexOf("$");
232
+ appendWhereNode(group, buildOperatorNode(key.substring(0, lastDollarIndex), `$${key.substring(lastDollarIndex + 1)}`, value));
233
+ return;
234
+ }
235
+
236
+ if (value && typeof value === "object" && !Array.isArray(value)) {
237
+ for (const [operator, operatorValue] of Object.entries(value)) {
238
+ appendWhereNode(group, buildOperatorNode(key, operator, operatorValue));
239
+ }
240
+ return;
241
+ }
242
+
243
+ appendWhereNode(group, buildOperatorNode(key, "=", value));
244
+ }
245
+
246
+ function buildArrayOperatorNode(fieldName, operator, value, errorFactory, emptyMessage) {
247
+ if (!Array.isArray(value)) {
248
+ throw new Error(errorFactory(operator), {
249
+ cause: null,
250
+ code: "validation"
251
+ });
252
+ }
253
+ if (value.length === 0) {
254
+ throw new Error(emptyMessage, {
255
+ cause: null,
256
+ code: "validation"
257
+ });
258
+ }
259
+
260
+ return { type: "op", field: fieldName, operator: operator, value: value };
261
+ }
262
+
263
+ function buildRangeOrNullOperatorNode(fieldName, operator, value) {
264
+ if (operator === "$between" || operator === "$notBetween") {
265
+ if (Array.isArray(value) && value.length === 2) {
266
+ return { type: "op", field: fieldName, operator: operator, value: value };
267
+ }
268
+ return null;
269
+ }
270
+
271
+ if (value === true) {
272
+ return { type: "op", field: fieldName, operator: operator, value: value };
273
+ }
274
+
275
+ return null;
226
276
  }
227
277
 
228
278
  function buildOperatorNode(fieldName, operator, value) {
229
279
  switch (operator) {
230
280
  case "$in":
231
- if (!Array.isArray(value)) {
232
- throw new Error(SqlErrors.IN_NEED_ARRAY(operator), {
233
- cause: null,
234
- code: "validation"
235
- });
236
- }
237
- if (value.length === 0) {
238
- throw new Error(SqlErrors.IN_NEED_NON_EMPTY, {
239
- cause: null,
240
- code: "validation"
241
- });
242
- }
243
- return { type: "op", field: fieldName, operator: operator, value: value };
281
+ return buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$in 操作符的值必须是数组 (operator: ${currentOperator})`, "$in 操作符的数组不能为空。提示:空数组会导致查询永远不匹配任何记录,这通常不是预期行为。请检查查询条件或移除该字段。");
244
282
  case "$nin":
245
283
  case "$notIn":
246
- if (!Array.isArray(value)) {
247
- throw new Error(SqlErrors.NIN_NEED_ARRAY(operator), {
248
- cause: null,
249
- code: "validation"
250
- });
251
- }
252
- if (value.length === 0) {
253
- throw new Error(SqlErrors.NIN_NEED_NON_EMPTY, {
254
- cause: null,
255
- code: "validation"
256
- });
257
- }
258
- return { type: "op", field: fieldName, operator: operator, value: value };
284
+ return buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$nin/$notIn 操作符的值必须是数组 (operator: ${currentOperator})`, "$nin/$notIn 操作符的数组不能为空。提示:空数组会导致查询匹配所有记录,这通常不是预期行为。请检查查询条件或移除该字段。");
259
285
  case "$between":
260
286
  case "$notBetween":
261
- if (Array.isArray(value) && value.length === 2) {
262
- return { type: "op", field: fieldName, operator: operator, value: value };
263
- }
264
- return null;
265
287
  case "$null":
266
288
  case "$notNull":
267
- if (value === true) {
268
- return { type: "op", field: fieldName, operator: operator, value: value };
269
- }
270
- return null;
289
+ return buildRangeOrNullOperatorNode(fieldName, operator, value);
271
290
  case "$like":
272
291
  case "like":
273
292
  case "$leftLike":
@@ -1,6 +1,46 @@
1
1
  import { isNullable, isString } from "../../utils/is.js";
2
2
  import { SqlCheck } from "./check.js";
3
- import { SqlErrors } from "./errors.js";
3
+
4
+ function isQuotedIdentPaired(value) {
5
+ const trimmed = value.trim();
6
+ if (trimmed.length < 2) {
7
+ return false;
8
+ }
9
+
10
+ const first = trimmed[0];
11
+ const last = trimmed[trimmed.length - 1];
12
+ return (first === "`" && last === "`") || (first === '"' && last === '"');
13
+ }
14
+
15
+ function startsWithIdentifierQuote(value) {
16
+ const trimmed = value.trim();
17
+ return trimmed.startsWith("`") || trimmed.startsWith('"');
18
+ }
19
+
20
+ function assertPairedQuotedIdent(value, label) {
21
+ if (startsWithIdentifierQuote(value) && !isQuotedIdentPaired(value)) {
22
+ throw new Error(`${label} 引用不完整,请使用成对的 \`...\` 或 "..." (value: ${value})`, {
23
+ cause: null,
24
+ code: "validation"
25
+ });
26
+ }
27
+ }
28
+
29
+ function escapeIdentifierPart(part, kind, quoteIdent, unpairedErrorFactory) {
30
+ if (isQuotedIdent(part)) {
31
+ return part;
32
+ }
33
+
34
+ if (startsWithQuote(part)) {
35
+ throw new Error(unpairedErrorFactory(part), {
36
+ cause: null,
37
+ code: "validation"
38
+ });
39
+ }
40
+
41
+ SqlCheck.assertSafeIdentifierPart(part, kind);
42
+ return quoteIdent(part);
43
+ }
4
44
 
5
45
  export function resolveQuoteIdent(options) {
6
46
  if (options && options.quoteIdent) {
@@ -9,7 +49,7 @@ export function resolveQuoteIdent(options) {
9
49
 
10
50
  return (identifier) => {
11
51
  if (!isString(identifier)) {
12
- throw new Error(SqlErrors.QUOTE_IDENT_NEED_STRING(identifier), {
52
+ throw new Error(`quoteIdent 需要字符串类型标识符 (identifier: ${String(identifier)})`, {
13
53
  cause: null,
14
54
  code: "validation"
15
55
  });
@@ -17,7 +57,7 @@ export function resolveQuoteIdent(options) {
17
57
 
18
58
  const trimmed = identifier.trim();
19
59
  if (!trimmed) {
20
- throw new Error(SqlErrors.IDENT_EMPTY, {
60
+ throw new Error("SQL 标识符不能为空", {
21
61
  cause: null,
22
62
  code: "validation"
23
63
  });
@@ -29,11 +69,11 @@ export function resolveQuoteIdent(options) {
29
69
  }
30
70
 
31
71
  export function isQuotedIdent(value) {
32
- return SqlCheck.isQuotedIdentPaired(value);
72
+ return isQuotedIdentPaired(value);
33
73
  }
34
74
 
35
75
  export function startsWithQuote(value) {
36
- return SqlCheck.startsWithQuote(value);
76
+ return startsWithIdentifierQuote(value);
37
77
  }
38
78
 
39
79
  export function escapeField(field, quoteIdent) {
@@ -43,7 +83,7 @@ export function escapeField(field, quoteIdent) {
43
83
 
44
84
  const trimmed = field.trim();
45
85
 
46
- SqlCheck.assertPairedQuotedIdentIfStartsWithQuote(trimmed, "字段标识符");
86
+ assertPairedQuotedIdent(trimmed, "字段标识符");
47
87
 
48
88
  if (trimmed === "*" || isQuotedIdent(trimmed)) {
49
89
  return trimmed;
@@ -52,7 +92,7 @@ export function escapeField(field, quoteIdent) {
52
92
  try {
53
93
  SqlCheck.assertNoExprField(trimmed);
54
94
  } catch {
55
- throw new Error(SqlErrors.FIELD_EXPR_NOT_ALLOWED(trimmed), {
95
+ throw new Error(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`, {
56
96
  cause: null,
57
97
  code: "validation"
58
98
  });
@@ -61,7 +101,7 @@ export function escapeField(field, quoteIdent) {
61
101
  if (trimmed.toUpperCase().includes(" AS ")) {
62
102
  const parts = trimmed.split(/\s+AS\s+/i);
63
103
  if (parts.length !== 2) {
64
- throw new Error(SqlErrors.FIELD_INVALID(trimmed), {
104
+ throw new Error(`字段格式非法,请使用简单字段名或安全引用,复杂表达式请使用 selectRaw/whereRaw (field: ${trimmed})`, {
65
105
  cause: null,
66
106
  code: "validation"
67
107
  });
@@ -71,7 +111,14 @@ export function escapeField(field, quoteIdent) {
71
111
  const aliasPart = parts[1];
72
112
  const cleanFieldPart = fieldPart.trim();
73
113
  const cleanAliasPart = aliasPart.trim();
74
- SqlCheck.assertSafeAlias(cleanAliasPart);
114
+ if (!isQuotedIdent(cleanAliasPart)) {
115
+ if (!SqlCheck.SAFE_IDENTIFIER_RE.test(cleanAliasPart)) {
116
+ throw new Error(`无效的字段别名: ${cleanAliasPart}`, {
117
+ cause: null,
118
+ code: "validation"
119
+ });
120
+ }
121
+ }
75
122
  return `${escapeField(cleanFieldPart, quoteIdent)} AS ${cleanAliasPart}`;
76
123
  }
77
124
 
@@ -83,7 +130,7 @@ export function escapeField(field, quoteIdent) {
83
130
  if (cleanPart === "*" || isQuotedIdent(cleanPart)) {
84
131
  return cleanPart;
85
132
  }
86
- SqlCheck.assertPairedQuotedIdentIfStartsWithQuote(cleanPart, "字段标识符");
133
+ assertPairedQuotedIdent(cleanPart, "字段标识符");
87
134
  return quoteIdent(cleanPart);
88
135
  })
89
136
  .join(".");
@@ -105,14 +152,14 @@ export function escapeTable(table, quoteIdent) {
105
152
 
106
153
  const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
107
154
  if (parts.length === 0) {
108
- throw new Error(SqlErrors.FROM_EMPTY, {
155
+ throw new Error("FROM 表名不能为空", {
109
156
  cause: null,
110
157
  code: "validation"
111
158
  });
112
159
  }
113
160
 
114
161
  if (parts.length > 2) {
115
- throw new Error(SqlErrors.TABLE_REF_TOO_MANY_PARTS(trimmed), {
162
+ throw new Error(`不支持的表引用格式(包含过多片段)。请使用 fromRaw 显式传入复杂表达式 (table: ${trimmed})`, {
116
163
  cause: null,
117
164
  code: "validation"
118
165
  });
@@ -124,7 +171,7 @@ export function escapeTable(table, quoteIdent) {
124
171
 
125
172
  const nameSegments = namePart.split(".");
126
173
  if (nameSegments.length > 2) {
127
- throw new Error(SqlErrors.TABLE_REF_SCHEMA_TOO_DEEP(trimmed), {
174
+ throw new Error(`不支持的表引用格式(schema 层级过深)。请使用 fromRaw (table: ${trimmed})`, {
128
175
  cause: null,
129
176
  code: "validation"
130
177
  });
@@ -137,49 +184,15 @@ export function escapeTable(table, quoteIdent) {
137
184
  const schema = schemaRaw.trim();
138
185
  const tableName = tableNameRaw.trim();
139
186
 
140
- const escapedSchema = isQuotedIdent(schema)
141
- ? schema
142
- : (() => {
143
- if (startsWithQuote(schema) && !isQuotedIdent(schema)) {
144
- throw new Error(SqlErrors.SCHEMA_QUOTE_NOT_PAIRED(schema), {
145
- cause: null,
146
- code: "validation"
147
- });
148
- }
149
- SqlCheck.assertSafeIdentifierPart(schema, "schema");
150
- return quoteIdent(schema);
151
- })();
152
-
153
- const escapedTableName = isQuotedIdent(tableName)
154
- ? tableName
155
- : (() => {
156
- if (startsWithQuote(tableName) && !isQuotedIdent(tableName)) {
157
- throw new Error(SqlErrors.TABLE_QUOTE_NOT_PAIRED(tableName), {
158
- cause: null,
159
- code: "validation"
160
- });
161
- }
162
- SqlCheck.assertSafeIdentifierPart(tableName, "table");
163
- return quoteIdent(tableName);
164
- })();
187
+ const escapedSchema = escapeIdentifierPart(schema, "schema", quoteIdent, (part) => `schema 标识符引用不完整,请使用成对的 \`...\` 或 "..." (schema: ${part})`);
188
+ const escapedTableName = escapeIdentifierPart(tableName, "table", quoteIdent, (part) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${part})`);
165
189
 
166
190
  escapedName = `${escapedSchema}.${escapedTableName}`;
167
191
  } else {
168
192
  const tableNameRaw = nameSegments[0];
169
193
  const tableName = tableNameRaw.trim();
170
194
 
171
- if (isQuotedIdent(tableName)) {
172
- escapedName = tableName;
173
- } else {
174
- if (startsWithQuote(tableName) && !isQuotedIdent(tableName)) {
175
- throw new Error(SqlErrors.TABLE_QUOTE_NOT_PAIRED(tableName), {
176
- cause: null,
177
- code: "validation"
178
- });
179
- }
180
- SqlCheck.assertSafeIdentifierPart(tableName, "table");
181
- escapedName = quoteIdent(tableName);
182
- }
195
+ escapedName = escapeIdentifierPart(tableName, "table", quoteIdent, (part) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${part})`);
183
196
  }
184
197
 
185
198
  if (aliasPart) {
@@ -196,7 +209,7 @@ export function validateParam(value) {
196
209
 
197
210
  export function normalizeLimitValue(count, offset) {
198
211
  if (typeof count !== "number" || count < 0) {
199
- throw new Error(SqlErrors.LIMIT_MUST_NON_NEGATIVE(count), {
212
+ throw new Error(`LIMIT 数量必须是非负数 (count: ${String(count)})`, {
200
213
  cause: null,
201
214
  code: "validation"
202
215
  });
@@ -206,7 +219,7 @@ export function normalizeLimitValue(count, offset) {
206
219
  let offsetValue = null;
207
220
  if (!isNullable(offset)) {
208
221
  if (typeof offset !== "number" || offset < 0) {
209
- throw new Error(SqlErrors.OFFSET_MUST_NON_NEGATIVE(offset), {
222
+ throw new Error(`OFFSET 必须是非负数 (offset: ${String(offset)})`, {
210
223
  cause: null,
211
224
  code: "validation"
212
225
  });
@@ -219,7 +232,7 @@ export function normalizeLimitValue(count, offset) {
219
232
 
220
233
  export function normalizeOffsetValue(count) {
221
234
  if (typeof count !== "number" || count < 0) {
222
- throw new Error(SqlErrors.OFFSET_COUNT_MUST_NON_NEGATIVE(count), {
235
+ throw new Error(`OFFSET 必须是非负数 (count: ${String(count)})`, {
223
236
  cause: null,
224
237
  code: "validation"
225
238
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.20.8",
4
- "gitHead": "c5906450b26c4f674094344de80a750c5f753736",
3
+ "version": "3.20.9",
4
+ "gitHead": "c6107d7e5006ffeba25cedef95ae0c7a5d294673",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
7
7
  "keywords": [
package/lib/cacheKeys.js DELETED
@@ -1,42 +0,0 @@
1
- /**
2
- * Cache Key 统一管理
3
- * 所有缓存键在此统一定义,避免硬编码分散
4
- */
5
-
6
- /**
7
- * Cache Key 生成函数集合
8
- */
9
- export class CacheKeys {
10
- /** 所有接口缓存2 */
11
- static apisAll() {
12
- return "apis:all";
13
- }
14
-
15
- /** 所有菜单缓存 */
16
- static menusAll() {
17
- return "menus:all";
18
- }
19
-
20
- /** 角色信息缓存(完整角色对象) */
21
- static roleInfo(roleCode) {
22
- return `role:info:${roleCode}`;
23
- }
24
-
25
- /**
26
- * 角色菜单权限缓存(Set 集合)
27
- * - key: role:menus:${roleCode}
28
- * - member: menu.path(例如 /permission/role)
29
- */
30
- static roleMenus(roleCode) {
31
- return `role:menus:${roleCode}`;
32
- }
33
-
34
- /**
35
- * 角色接口权限缓存(Set 集合)
36
- * - key: role:apis:${roleCode}
37
- * - member: url.pathname(例如 /api/user/login;与 method 无关)
38
- */
39
- static roleApis(roleCode) {
40
- return `role:apis:${roleCode}`;
41
- }
42
- }