befly 3.20.11 → 3.21.0

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,273 +0,0 @@
1
- import { isNullable, isString } from "../../utils/is.js";
2
- import { SqlCheck } from "./check.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
- }
44
-
45
- export function resolveQuoteIdent(options) {
46
- if (options && options.quoteIdent) {
47
- return options.quoteIdent;
48
- }
49
-
50
- return (identifier) => {
51
- if (!isString(identifier)) {
52
- throw new Error(`quoteIdent 需要字符串类型标识符 (identifier: ${String(identifier)})`, {
53
- cause: null,
54
- code: "validation"
55
- });
56
- }
57
-
58
- const trimmed = identifier.trim();
59
- if (!trimmed) {
60
- throw new Error("SQL 标识符不能为空", {
61
- cause: null,
62
- code: "validation"
63
- });
64
- }
65
-
66
- const escaped = trimmed.replace(/`/g, "``");
67
- return `\`${escaped}\``;
68
- };
69
- }
70
-
71
- export function isQuotedIdent(value) {
72
- return isQuotedIdentPaired(value);
73
- }
74
-
75
- export function startsWithQuote(value) {
76
- return startsWithIdentifierQuote(value);
77
- }
78
-
79
- export function escapeField(field, quoteIdent) {
80
- if (!isString(field)) {
81
- return field;
82
- }
83
-
84
- const trimmed = field.trim();
85
-
86
- assertPairedQuotedIdent(trimmed, "字段标识符");
87
-
88
- if (trimmed === "*" || isQuotedIdent(trimmed)) {
89
- return trimmed;
90
- }
91
-
92
- try {
93
- SqlCheck.assertNoExprField(trimmed);
94
- } catch {
95
- throw new Error(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`, {
96
- cause: null,
97
- code: "validation"
98
- });
99
- }
100
-
101
- if (trimmed.toUpperCase().includes(" AS ")) {
102
- const parts = trimmed.split(/\s+AS\s+/i);
103
- if (parts.length !== 2) {
104
- throw new Error(`字段格式非法,请使用简单字段名或安全引用,复杂表达式请使用 selectRaw/whereRaw (field: ${trimmed})`, {
105
- cause: null,
106
- code: "validation"
107
- });
108
- }
109
-
110
- const fieldPart = parts[0];
111
- const aliasPart = parts[1];
112
- const cleanFieldPart = fieldPart.trim();
113
- const cleanAliasPart = aliasPart.trim();
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
- }
122
- return `${escapeField(cleanFieldPart, quoteIdent)} AS ${cleanAliasPart}`;
123
- }
124
-
125
- if (trimmed.includes(".")) {
126
- const parts = trimmed.split(".");
127
- return parts
128
- .map((part) => {
129
- const cleanPart = part.trim();
130
- if (cleanPart === "*" || isQuotedIdent(cleanPart)) {
131
- return cleanPart;
132
- }
133
- assertPairedQuotedIdent(cleanPart, "字段标识符");
134
- return quoteIdent(cleanPart);
135
- })
136
- .join(".");
137
- }
138
-
139
- return quoteIdent(trimmed);
140
- }
141
-
142
- export function escapeTable(table, quoteIdent) {
143
- if (!isString(table)) {
144
- return table;
145
- }
146
-
147
- const trimmed = table.trim();
148
-
149
- if (isQuotedIdent(trimmed)) {
150
- return trimmed;
151
- }
152
-
153
- const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
154
- if (parts.length === 0) {
155
- throw new Error("FROM 表名不能为空", {
156
- cause: null,
157
- code: "validation"
158
- });
159
- }
160
-
161
- if (parts.length > 2) {
162
- throw new Error(`不支持的表引用格式(包含过多片段)。请使用 fromRaw 显式传入复杂表达式 (table: ${trimmed})`, {
163
- cause: null,
164
- code: "validation"
165
- });
166
- }
167
-
168
- const namePart = parts[0].trim();
169
-
170
- const aliasPart = parts.length === 2 ? parts[1].trim() : null;
171
-
172
- const nameSegments = namePart.split(".");
173
- if (nameSegments.length > 2) {
174
- throw new Error(`不支持的表引用格式(schema 层级过深)。请使用 fromRaw (table: ${trimmed})`, {
175
- cause: null,
176
- code: "validation"
177
- });
178
- }
179
-
180
- let escapedName = "";
181
- if (nameSegments.length === 2) {
182
- const schemaRaw = nameSegments[0];
183
- const tableNameRaw = nameSegments[1];
184
- const schema = schemaRaw.trim();
185
- const tableName = tableNameRaw.trim();
186
-
187
- const escapedSchema = escapeIdentifierPart(schema, "schema", quoteIdent, (part) => `schema 标识符引用不完整,请使用成对的 \`...\` 或 "..." (schema: ${part})`);
188
- const escapedTableName = escapeIdentifierPart(tableName, "table", quoteIdent, (part) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${part})`);
189
-
190
- escapedName = `${escapedSchema}.${escapedTableName}`;
191
- } else {
192
- const tableNameRaw = nameSegments[0];
193
- const tableName = tableNameRaw.trim();
194
-
195
- escapedName = escapeIdentifierPart(tableName, "table", quoteIdent, (part) => `table 标识符引用不完整,请使用成对的 \`...\` 或 "..." (table: ${part})`);
196
- }
197
-
198
- if (aliasPart) {
199
- SqlCheck.assertSafeIdentifierPart(aliasPart, "alias");
200
- return `${escapedName} ${aliasPart}`;
201
- }
202
-
203
- return escapedName;
204
- }
205
-
206
- export function validateParam(value) {
207
- SqlCheck.assertNoUndefinedParam(value, "SQL 参数值");
208
- }
209
-
210
- export function normalizeLimitValue(count, offset) {
211
- if (typeof count !== "number" || count < 0) {
212
- throw new Error(`LIMIT 数量必须是非负数 (count: ${String(count)})`, {
213
- cause: null,
214
- code: "validation"
215
- });
216
- }
217
- const limitValue = Math.floor(count);
218
-
219
- let offsetValue = null;
220
- if (!isNullable(offset)) {
221
- if (typeof offset !== "number" || offset < 0) {
222
- throw new Error(`OFFSET 必须是非负数 (offset: ${String(offset)})`, {
223
- cause: null,
224
- code: "validation"
225
- });
226
- }
227
- offsetValue = Math.floor(offset);
228
- }
229
-
230
- return { limitValue: limitValue, offsetValue: offsetValue };
231
- }
232
-
233
- export function normalizeOffsetValue(count) {
234
- if (typeof count !== "number" || count < 0) {
235
- throw new Error(`OFFSET 必须是非负数 (count: ${String(count)})`, {
236
- cause: null,
237
- code: "validation"
238
- });
239
- }
240
- return Math.floor(count);
241
- }
242
-
243
- export function toSqlParams(params) {
244
- if (isNullable(params)) {
245
- return [];
246
- }
247
-
248
- if (!Array.isArray(params)) {
249
- throw new Error(`SQL 参数必须是数组,当前类型: ${typeof params}`, {
250
- cause: null,
251
- code: "validation"
252
- });
253
- }
254
-
255
- const out = [];
256
- for (const value of params) {
257
- if (value === undefined) {
258
- throw new Error("SQL 参数不能为 undefined", {
259
- cause: null,
260
- code: "validation"
261
- });
262
- }
263
-
264
- if (typeof value === "bigint") {
265
- out.push(value.toString());
266
- continue;
267
- }
268
-
269
- out.push(value);
270
- }
271
-
272
- return out;
273
- }