@truto/sqlite-builder 2.0.2-canary.28 → 2.0.2-canary.3
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.
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/filter.d.ts +6 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/fragment.d.ts +14 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +548 -0
- package/dist/index.js.map +13 -0
- package/dist/sql.d.ts +51 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/README.md +0 -1
- package/index.js +0 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common regex patterns and limits used across the codebase
|
|
3
|
+
*/
|
|
4
|
+
export declare const MAX_QUERY_LENGTH = 102400;
|
|
5
|
+
export declare const MAX_IDENTIFIER_LENGTH = 255;
|
|
6
|
+
export declare const MAX_PATTERN_LENGTH = 1024;
|
|
7
|
+
export declare const STACKED_QUERY_REGEX: RegExp;
|
|
8
|
+
export declare const QUALIFIED_IDENTIFIER_REGEX: RegExp;
|
|
9
|
+
export declare const SIMPLE_IDENTIFIER_REGEX: RegExp;
|
|
10
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAKtC,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAMxC,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAMtC,eAAO,MAAM,mBAAmB,QAAe,CAAA;AAG/C,eAAO,MAAM,0BAA0B,QACgB,CAAA;AAGvD,eAAO,MAAM,uBAAuB,QAA6B,CAAA"}
|
package/dist/filter.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAGV,YAAY,EACZ,UAAU,EACX,MAAM,SAAS,CAAA;AAgZhB;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAuB9D"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SqlFragment } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create and register an immutable, branded SQL fragment.
|
|
4
|
+
*
|
|
5
|
+
* The returned object is frozen (including its values array) so callers cannot
|
|
6
|
+
* mutate a fragment after the integrity checks that produced it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createFragment(text: string, values: readonly unknown[]): SqlFragment;
|
|
9
|
+
/**
|
|
10
|
+
* Type guard: was this value minted by the library (and therefore trusted to
|
|
11
|
+
* contribute raw SQL text)?
|
|
12
|
+
*/
|
|
13
|
+
export declare function isSqlFragment(value: unknown): value is SqlFragment;
|
|
14
|
+
//# sourceMappingURL=fragment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragment.d.ts","sourceRoot":"","sources":["../src/fragment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAkB1C;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,OAAO,EAAE,GACzB,WAAW,CASb;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAMlE"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* truto-sqlite-builder - Safe, zero-dependency template-literal tag for SQLite queries
|
|
3
|
+
*/
|
|
4
|
+
export { compileFilter } from './filter';
|
|
5
|
+
export { sql } from './sql';
|
|
6
|
+
export type { FilterResult, JsonFilter, SqlQuery } from './types';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAC3B,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var MAX_QUERY_LENGTH = 102400;
|
|
3
|
+
var MAX_IDENTIFIER_LENGTH = 255;
|
|
4
|
+
var MAX_PATTERN_LENGTH = 1024;
|
|
5
|
+
var STACKED_QUERY_REGEX = /;[\s\S]*\S/;
|
|
6
|
+
var QUALIFIED_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
7
|
+
var SIMPLE_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
8
|
+
|
|
9
|
+
// src/fragment.ts
|
|
10
|
+
var fragmentRegistry = new WeakSet;
|
|
11
|
+
function createFragment(text, values) {
|
|
12
|
+
const fragment = Object.freeze({
|
|
13
|
+
text,
|
|
14
|
+
values: Object.freeze([...values])
|
|
15
|
+
});
|
|
16
|
+
fragmentRegistry.add(fragment);
|
|
17
|
+
return fragment;
|
|
18
|
+
}
|
|
19
|
+
function isSqlFragment(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && fragmentRegistry.has(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/sql.ts
|
|
24
|
+
function formatDate(date) {
|
|
25
|
+
const year = date.getFullYear();
|
|
26
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
27
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
28
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
29
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
30
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
31
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
32
|
+
}
|
|
33
|
+
function sqlValue(value) {
|
|
34
|
+
if (value === null || value === undefined) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "string") {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (value instanceof Date) {
|
|
44
|
+
return formatDate(value);
|
|
45
|
+
}
|
|
46
|
+
if (value instanceof Buffer || value instanceof Uint8Array) {
|
|
47
|
+
throw new TypeError("Buffer/Uint8Array values must be used with sql.blob() for safe BLOB handling");
|
|
48
|
+
}
|
|
49
|
+
throw new TypeError(`Unsupported value type: ${typeof value}`);
|
|
50
|
+
}
|
|
51
|
+
function quoteSingleIdentifier(identifier) {
|
|
52
|
+
if (identifier.length > MAX_IDENTIFIER_LENGTH) {
|
|
53
|
+
throw new TypeError(`Identifier part too long: ${identifier.length} characters (max: ${MAX_IDENTIFIER_LENGTH})`);
|
|
54
|
+
}
|
|
55
|
+
if (!SIMPLE_IDENTIFIER_REGEX.test(identifier)) {
|
|
56
|
+
throw new TypeError(`Invalid identifier part: ${identifier}. Must be a valid ANSI identifier.`);
|
|
57
|
+
}
|
|
58
|
+
return `"${identifier}"`;
|
|
59
|
+
}
|
|
60
|
+
function quoteQualifiedIdentifier(identifier) {
|
|
61
|
+
if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {
|
|
62
|
+
throw new TypeError(`Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
63
|
+
}
|
|
64
|
+
const parts = identifier.split(".");
|
|
65
|
+
return parts.map(quoteSingleIdentifier).join(".");
|
|
66
|
+
}
|
|
67
|
+
function sqlIdent(identifier) {
|
|
68
|
+
if (Array.isArray(identifier)) {
|
|
69
|
+
if (identifier.length === 0) {
|
|
70
|
+
throw new TypeError("Identifier array cannot be empty");
|
|
71
|
+
}
|
|
72
|
+
const fragments = [];
|
|
73
|
+
for (const item of identifier) {
|
|
74
|
+
if (isSqlFragment(item)) {
|
|
75
|
+
fragments.push(item);
|
|
76
|
+
} else if (typeof item === "string") {
|
|
77
|
+
if (!item) {
|
|
78
|
+
throw new TypeError("All identifiers must be non-empty strings");
|
|
79
|
+
}
|
|
80
|
+
if (!QUALIFIED_IDENTIFIER_REGEX.test(item)) {
|
|
81
|
+
throw new TypeError(`Invalid identifier: ${item}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
82
|
+
}
|
|
83
|
+
fragments.push(createFragment(quoteQualifiedIdentifier(item), []));
|
|
84
|
+
} else {
|
|
85
|
+
throw new TypeError("Array items must be strings or SQL fragments");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const text = fragments.map((f) => f.text).join(", ");
|
|
89
|
+
const values = fragments.flatMap((f) => [...f.values]);
|
|
90
|
+
return createFragment(text, values);
|
|
91
|
+
}
|
|
92
|
+
if (!identifier || typeof identifier !== "string") {
|
|
93
|
+
throw new TypeError("Identifier must be a non-empty string");
|
|
94
|
+
}
|
|
95
|
+
if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {
|
|
96
|
+
throw new TypeError(`Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
97
|
+
}
|
|
98
|
+
return createFragment(quoteQualifiedIdentifier(identifier), []);
|
|
99
|
+
}
|
|
100
|
+
function sqlIn(array) {
|
|
101
|
+
if (!Array.isArray(array)) {
|
|
102
|
+
throw new TypeError("sql.in() requires an array");
|
|
103
|
+
}
|
|
104
|
+
if (array.length === 0) {
|
|
105
|
+
throw new TypeError("sql.in() cannot be used with empty arrays");
|
|
106
|
+
}
|
|
107
|
+
if (array.length > 1000) {
|
|
108
|
+
console.warn(`sql.in(): Large array with ${array.length} items. Consider using temporary tables for better performance.`);
|
|
109
|
+
}
|
|
110
|
+
const placeholders = array.map(() => "?").join(",");
|
|
111
|
+
const values = array.map(sqlValue);
|
|
112
|
+
return createFragment(`(${placeholders})`, values);
|
|
113
|
+
}
|
|
114
|
+
function sqlRaw(rawSql) {
|
|
115
|
+
if (typeof rawSql !== "string") {
|
|
116
|
+
throw new TypeError("sql.raw() requires a string");
|
|
117
|
+
}
|
|
118
|
+
return createFragment(rawSql, []);
|
|
119
|
+
}
|
|
120
|
+
function sqlBlob(data) {
|
|
121
|
+
if (!(data instanceof Buffer) && !(data instanceof Uint8Array)) {
|
|
122
|
+
throw new TypeError("sql.blob() requires a Buffer or Uint8Array");
|
|
123
|
+
}
|
|
124
|
+
return createFragment("?", [data]);
|
|
125
|
+
}
|
|
126
|
+
var SEPARATOR_FORBIDDEN_TOKENS = [
|
|
127
|
+
"'",
|
|
128
|
+
'"',
|
|
129
|
+
"`",
|
|
130
|
+
"[",
|
|
131
|
+
"]",
|
|
132
|
+
";",
|
|
133
|
+
"\\",
|
|
134
|
+
"\x00",
|
|
135
|
+
"--",
|
|
136
|
+
"/*",
|
|
137
|
+
"*/"
|
|
138
|
+
];
|
|
139
|
+
function assertSafeSeparator(separator) {
|
|
140
|
+
for (const token of SEPARATOR_FORBIDDEN_TOKENS) {
|
|
141
|
+
if (separator.includes(token)) {
|
|
142
|
+
throw new TypeError(`Unsafe sql.join() separator: contains forbidden token ${JSON.stringify(token)}. Pass a SqlFragment (e.g. sql.raw) if you need parameterized separators.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
let depth = 0;
|
|
146
|
+
for (const char of separator) {
|
|
147
|
+
if (char === "(") {
|
|
148
|
+
depth++;
|
|
149
|
+
} else if (char === ")") {
|
|
150
|
+
depth--;
|
|
151
|
+
if (depth < 0) {
|
|
152
|
+
throw new TypeError("Unsafe sql.join() separator: unbalanced parentheses");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (depth !== 0) {
|
|
157
|
+
throw new TypeError("Unsafe sql.join() separator: unbalanced parentheses");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function sqlJoin(fragments, separator = ", ") {
|
|
161
|
+
if (!Array.isArray(fragments)) {
|
|
162
|
+
throw new TypeError("sql.join() requires an array of fragments");
|
|
163
|
+
}
|
|
164
|
+
for (const fragment of fragments) {
|
|
165
|
+
if (!isSqlFragment(fragment)) {
|
|
166
|
+
throw new TypeError("sql.join() requires SQL fragments created by the sql tag or its helpers");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (fragments.length === 0) {
|
|
170
|
+
return createFragment("", []);
|
|
171
|
+
}
|
|
172
|
+
let separatorText;
|
|
173
|
+
let separatorValues = [];
|
|
174
|
+
if (isSqlFragment(separator)) {
|
|
175
|
+
separatorText = separator.text;
|
|
176
|
+
separatorValues = separator.values;
|
|
177
|
+
} else if (typeof separator === "string") {
|
|
178
|
+
assertSafeSeparator(separator);
|
|
179
|
+
separatorText = separator;
|
|
180
|
+
} else {
|
|
181
|
+
throw new TypeError("sql.join() separator must be a string or a SQL fragment");
|
|
182
|
+
}
|
|
183
|
+
let text = "";
|
|
184
|
+
const values = [];
|
|
185
|
+
fragments.forEach((fragment, index) => {
|
|
186
|
+
if (index > 0) {
|
|
187
|
+
text += separatorText;
|
|
188
|
+
values.push(...separatorValues);
|
|
189
|
+
}
|
|
190
|
+
text += fragment.text;
|
|
191
|
+
values.push(...fragment.values);
|
|
192
|
+
});
|
|
193
|
+
return createFragment(text, values);
|
|
194
|
+
}
|
|
195
|
+
function scanSql(text) {
|
|
196
|
+
let placeholderCount = 0;
|
|
197
|
+
let code = "";
|
|
198
|
+
let i = 0;
|
|
199
|
+
const length = text.length;
|
|
200
|
+
while (i < length) {
|
|
201
|
+
const char = text[i];
|
|
202
|
+
const next = text[i + 1];
|
|
203
|
+
if (char === "-" && next === "-") {
|
|
204
|
+
i += 2;
|
|
205
|
+
while (i < length && text[i] !== `
|
|
206
|
+
`) {
|
|
207
|
+
i++;
|
|
208
|
+
}
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (char === "/" && next === "*") {
|
|
212
|
+
i += 2;
|
|
213
|
+
while (i < length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
i += 2;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
220
|
+
const quote = char;
|
|
221
|
+
i++;
|
|
222
|
+
while (i < length) {
|
|
223
|
+
if (text[i] === quote) {
|
|
224
|
+
if (text[i + 1] === quote) {
|
|
225
|
+
i += 2;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
i++;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
i++;
|
|
232
|
+
}
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (char === "[") {
|
|
236
|
+
i++;
|
|
237
|
+
while (i < length && text[i] !== "]") {
|
|
238
|
+
i++;
|
|
239
|
+
}
|
|
240
|
+
i++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (char === "?") {
|
|
244
|
+
placeholderCount++;
|
|
245
|
+
}
|
|
246
|
+
code += char;
|
|
247
|
+
i++;
|
|
248
|
+
}
|
|
249
|
+
return { placeholderCount, code };
|
|
250
|
+
}
|
|
251
|
+
function sql(strings, ...values) {
|
|
252
|
+
let text = strings[0] || "";
|
|
253
|
+
const queryValues = [];
|
|
254
|
+
for (let i = 0;i < values.length; i++) {
|
|
255
|
+
const value = values[i];
|
|
256
|
+
if (isSqlFragment(value)) {
|
|
257
|
+
text += value.text;
|
|
258
|
+
queryValues.push(...value.values);
|
|
259
|
+
} else {
|
|
260
|
+
text += "?";
|
|
261
|
+
queryValues.push(sqlValue(value));
|
|
262
|
+
}
|
|
263
|
+
text += strings[i + 1] || "";
|
|
264
|
+
}
|
|
265
|
+
if (text.length > MAX_QUERY_LENGTH) {
|
|
266
|
+
throw new Error(`Query too long: ${text.length} bytes (max: ${MAX_QUERY_LENGTH})`);
|
|
267
|
+
}
|
|
268
|
+
const { placeholderCount, code } = scanSql(text);
|
|
269
|
+
if (placeholderCount !== queryValues.length) {
|
|
270
|
+
throw new Error(`Placeholder count (${placeholderCount}) does not match bound value count (${queryValues.length}). ` + 'Did a raw fragment contain a "?" without supplying its value?');
|
|
271
|
+
}
|
|
272
|
+
if (STACKED_QUERY_REGEX.test(code)) {
|
|
273
|
+
throw new Error("Stacked queries are not allowed");
|
|
274
|
+
}
|
|
275
|
+
return createFragment(text, queryValues);
|
|
276
|
+
}
|
|
277
|
+
sql.value = sqlValue;
|
|
278
|
+
sql.ident = sqlIdent;
|
|
279
|
+
sql.in = sqlIn;
|
|
280
|
+
sql.raw = sqlRaw;
|
|
281
|
+
sql.blob = sqlBlob;
|
|
282
|
+
sql.join = sqlJoin;
|
|
283
|
+
|
|
284
|
+
// src/filter.ts
|
|
285
|
+
var MAX_NESTING_DEPTH = 10;
|
|
286
|
+
var MAX_OPERATORS = 100;
|
|
287
|
+
var VALID_OPERATORS = new Set([
|
|
288
|
+
"gt",
|
|
289
|
+
"gte",
|
|
290
|
+
"lt",
|
|
291
|
+
"lte",
|
|
292
|
+
"ne",
|
|
293
|
+
"in",
|
|
294
|
+
"nin",
|
|
295
|
+
"like",
|
|
296
|
+
"ilike",
|
|
297
|
+
"regex",
|
|
298
|
+
"exists",
|
|
299
|
+
"and",
|
|
300
|
+
"or"
|
|
301
|
+
]);
|
|
302
|
+
function isJsonPath(field) {
|
|
303
|
+
return field.includes(".");
|
|
304
|
+
}
|
|
305
|
+
function compileJsonPath(field) {
|
|
306
|
+
const parts = field.split(".");
|
|
307
|
+
const columnName = parts[0];
|
|
308
|
+
const jsonPath = parts.slice(1);
|
|
309
|
+
if (!columnName || jsonPath.length === 0 || jsonPath.some((part) => !part)) {
|
|
310
|
+
throw new SyntaxError(`Invalid JSON path: ${field}`);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
columnName,
|
|
314
|
+
jsonPath: "$." + jsonPath.join(".")
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function isValidSqlIdentifier(identifier) {
|
|
318
|
+
return identifier.length <= MAX_IDENTIFIER_LENGTH && SIMPLE_IDENTIFIER_REGEX.test(identifier);
|
|
319
|
+
}
|
|
320
|
+
function assertPatternWithinLimit(operator, pattern) {
|
|
321
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
322
|
+
throw new RangeError(`${operator} pattern too long: ${pattern.length} characters (max: ${MAX_PATTERN_LENGTH})`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function compileFieldCondition(field, condition, context, alias) {
|
|
326
|
+
if (condition === null || condition === undefined || typeof condition === "string" || typeof condition === "number" || typeof condition === "boolean" || condition instanceof Date) {
|
|
327
|
+
context.operatorCount++;
|
|
328
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
329
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
330
|
+
}
|
|
331
|
+
if (isJsonPath(field)) {
|
|
332
|
+
const { columnName, jsonPath } = compileJsonPath(field);
|
|
333
|
+
const identFragment2 = sql.ident(columnName);
|
|
334
|
+
const fullFieldExpr = alias ? `${alias}.${identFragment2.text}` : identFragment2.text;
|
|
335
|
+
if (condition === null || condition === undefined) {
|
|
336
|
+
context.values.push(jsonPath);
|
|
337
|
+
return `(json_extract(${fullFieldExpr}, ?) IS NULL)`;
|
|
338
|
+
} else {
|
|
339
|
+
context.values.push(jsonPath, sql.value(condition));
|
|
340
|
+
return `(json_extract(${fullFieldExpr}, ?) = ?)`;
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
const identFragment2 = sql.ident(field);
|
|
344
|
+
const fullFieldExpr = alias ? `${alias}.${identFragment2.text}` : identFragment2.text;
|
|
345
|
+
if (condition === null || condition === undefined) {
|
|
346
|
+
return `(${fullFieldExpr} IS NULL)`;
|
|
347
|
+
} else {
|
|
348
|
+
context.values.push(sql.value(condition));
|
|
349
|
+
return `(${fullFieldExpr} = ?)`;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (typeof condition !== "object" || condition === null) {
|
|
354
|
+
throw new TypeError("Condition must be a value or operator object");
|
|
355
|
+
}
|
|
356
|
+
const operators = condition;
|
|
357
|
+
const clauses = [];
|
|
358
|
+
for (const op of Object.keys(operators)) {
|
|
359
|
+
if (!VALID_OPERATORS.has(op)) {
|
|
360
|
+
throw new SyntaxError(`Unknown operator: ${op}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const identFragment = sql.ident(isJsonPath(field) ? compileJsonPath(field).columnName : field);
|
|
364
|
+
const baseFieldExpr = alias ? `${alias}.${identFragment.text}` : identFragment.text;
|
|
365
|
+
const fieldExpr = isJsonPath(field) ? `json_extract(${baseFieldExpr}, ?)` : baseFieldExpr;
|
|
366
|
+
if (isJsonPath(field)) {
|
|
367
|
+
context.values.push(compileJsonPath(field).jsonPath);
|
|
368
|
+
}
|
|
369
|
+
if ("exists" in operators) {
|
|
370
|
+
context.operatorCount++;
|
|
371
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
372
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
373
|
+
}
|
|
374
|
+
return operators.exists ? `(${fieldExpr} IS NOT NULL)` : `(${fieldExpr} IS NULL)`;
|
|
375
|
+
}
|
|
376
|
+
for (const [op, value] of Object.entries(operators)) {
|
|
377
|
+
context.operatorCount++;
|
|
378
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
379
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
380
|
+
}
|
|
381
|
+
switch (op) {
|
|
382
|
+
case "gt":
|
|
383
|
+
context.values.push(sql.value(value));
|
|
384
|
+
clauses.push(`${fieldExpr} > ?`);
|
|
385
|
+
break;
|
|
386
|
+
case "gte":
|
|
387
|
+
context.values.push(sql.value(value));
|
|
388
|
+
clauses.push(`${fieldExpr} >= ?`);
|
|
389
|
+
break;
|
|
390
|
+
case "lt":
|
|
391
|
+
context.values.push(sql.value(value));
|
|
392
|
+
clauses.push(`${fieldExpr} < ?`);
|
|
393
|
+
break;
|
|
394
|
+
case "lte":
|
|
395
|
+
context.values.push(sql.value(value));
|
|
396
|
+
clauses.push(`${fieldExpr} <= ?`);
|
|
397
|
+
break;
|
|
398
|
+
case "ne":
|
|
399
|
+
if (value === null || value === undefined) {
|
|
400
|
+
clauses.push(`${fieldExpr} IS NOT NULL`);
|
|
401
|
+
} else {
|
|
402
|
+
context.values.push(sql.value(value));
|
|
403
|
+
clauses.push(`${fieldExpr} <> ?`);
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
case "in": {
|
|
407
|
+
if (!Array.isArray(value)) {
|
|
408
|
+
throw new TypeError("IN operator requires an array");
|
|
409
|
+
}
|
|
410
|
+
if (value.length === 0) {
|
|
411
|
+
throw new TypeError("IN operator cannot be used with empty arrays");
|
|
412
|
+
}
|
|
413
|
+
if (value.length > 999) {
|
|
414
|
+
throw new RangeError("IN operator cannot be used with arrays larger than 999 items");
|
|
415
|
+
}
|
|
416
|
+
const inFragment = sql.in(value);
|
|
417
|
+
context.values.push(...inFragment.values);
|
|
418
|
+
clauses.push(`${fieldExpr} IN ${inFragment.text}`);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case "nin": {
|
|
422
|
+
if (!Array.isArray(value)) {
|
|
423
|
+
throw new TypeError("NIN operator requires an array");
|
|
424
|
+
}
|
|
425
|
+
if (value.length === 0) {
|
|
426
|
+
throw new TypeError("NIN operator cannot be used with empty arrays");
|
|
427
|
+
}
|
|
428
|
+
if (value.length > 999) {
|
|
429
|
+
throw new RangeError("NIN operator cannot be used with arrays larger than 999 items");
|
|
430
|
+
}
|
|
431
|
+
const ninFragment = sql.in(value);
|
|
432
|
+
context.values.push(...ninFragment.values);
|
|
433
|
+
clauses.push(`${fieldExpr} NOT IN ${ninFragment.text}`);
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case "like":
|
|
437
|
+
if (typeof value !== "string") {
|
|
438
|
+
throw new TypeError("LIKE operator requires a string pattern");
|
|
439
|
+
}
|
|
440
|
+
assertPatternWithinLimit("LIKE", value);
|
|
441
|
+
context.values.push(value);
|
|
442
|
+
clauses.push(`${fieldExpr} LIKE ?`);
|
|
443
|
+
break;
|
|
444
|
+
case "ilike":
|
|
445
|
+
if (typeof value !== "string") {
|
|
446
|
+
throw new TypeError("ILIKE operator requires a string pattern");
|
|
447
|
+
}
|
|
448
|
+
assertPatternWithinLimit("ILIKE", value);
|
|
449
|
+
context.values.push(value);
|
|
450
|
+
clauses.push(`${fieldExpr} LIKE ? COLLATE NOCASE`);
|
|
451
|
+
break;
|
|
452
|
+
case "regex":
|
|
453
|
+
if (typeof value !== "string") {
|
|
454
|
+
throw new TypeError("REGEX operator requires a string pattern");
|
|
455
|
+
}
|
|
456
|
+
assertPatternWithinLimit("REGEX", value);
|
|
457
|
+
context.values.push(value);
|
|
458
|
+
clauses.push(`${fieldExpr} REGEXP ?`);
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (clauses.length === 0) {
|
|
463
|
+
throw new SyntaxError("Operator object must contain at least one valid operator");
|
|
464
|
+
}
|
|
465
|
+
return clauses.length === 1 ? `(${clauses[0]})` : `(${clauses.join(" AND ")})`;
|
|
466
|
+
}
|
|
467
|
+
function compileFilterRecursive(filter, context, alias) {
|
|
468
|
+
if (context.depth >= MAX_NESTING_DEPTH) {
|
|
469
|
+
throw new RangeError(`Nesting depth too deep (max: ${MAX_NESTING_DEPTH})`);
|
|
470
|
+
}
|
|
471
|
+
if (typeof filter !== "object" || filter === null) {
|
|
472
|
+
throw new TypeError("Filter must be an object");
|
|
473
|
+
}
|
|
474
|
+
context.depth++;
|
|
475
|
+
const clauses = [];
|
|
476
|
+
if ("and" in filter && filter.and) {
|
|
477
|
+
context.operatorCount++;
|
|
478
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
479
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
480
|
+
}
|
|
481
|
+
if (!Array.isArray(filter.and)) {
|
|
482
|
+
throw new TypeError("AND operator must be an array");
|
|
483
|
+
}
|
|
484
|
+
if (filter.and.length === 0) {
|
|
485
|
+
throw new TypeError("AND operator cannot be used with empty arrays");
|
|
486
|
+
}
|
|
487
|
+
const andClauses = filter.and.map((subFilter) => compileFilterRecursive(subFilter, context, alias));
|
|
488
|
+
clauses.push(`(${andClauses.join(" AND ")})`);
|
|
489
|
+
}
|
|
490
|
+
if ("or" in filter && filter.or) {
|
|
491
|
+
context.operatorCount++;
|
|
492
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
493
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
494
|
+
}
|
|
495
|
+
if (!Array.isArray(filter.or)) {
|
|
496
|
+
throw new TypeError("OR operator must be an array");
|
|
497
|
+
}
|
|
498
|
+
if (filter.or.length === 0) {
|
|
499
|
+
throw new TypeError("OR operator cannot be used with empty arrays");
|
|
500
|
+
}
|
|
501
|
+
const orClauses = filter.or.map((subFilter) => compileFilterRecursive(subFilter, context, alias));
|
|
502
|
+
clauses.push(`(${orClauses.join(" OR ")})`);
|
|
503
|
+
}
|
|
504
|
+
const fieldEntries = Object.entries(filter).filter(([field, condition]) => field !== "and" && field !== "or" && !field.startsWith("$") && condition !== undefined);
|
|
505
|
+
for (const [field, condition] of fieldEntries) {
|
|
506
|
+
if (Array.isArray(condition)) {
|
|
507
|
+
throw new SyntaxError(`Field '${field}' cannot have array value. Use logical operators 'and'/'or' instead.`);
|
|
508
|
+
}
|
|
509
|
+
clauses.push(compileFieldCondition(field, condition, context, alias));
|
|
510
|
+
}
|
|
511
|
+
const aliasEntries = Object.entries(filter).filter(([key, value]) => key.startsWith("$") && value !== undefined && typeof value === "object" && value !== null);
|
|
512
|
+
for (const [aliasKey, aliasFilter] of aliasEntries) {
|
|
513
|
+
const aliasName = aliasKey.slice(1);
|
|
514
|
+
if (!isValidSqlIdentifier(aliasName)) {
|
|
515
|
+
throw new SyntaxError(`Invalid alias identifier: ${aliasName}`);
|
|
516
|
+
}
|
|
517
|
+
const aliasClause = compileFilterRecursive(aliasFilter, context, aliasName);
|
|
518
|
+
clauses.push(aliasClause);
|
|
519
|
+
}
|
|
520
|
+
context.depth--;
|
|
521
|
+
if (clauses.length === 0) {
|
|
522
|
+
throw new SyntaxError("Filter must contain at least one condition");
|
|
523
|
+
}
|
|
524
|
+
if (clauses.length === 1) {
|
|
525
|
+
return clauses[0];
|
|
526
|
+
} else {
|
|
527
|
+
return `(${clauses.join(" AND ")})`;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function compileFilter(filter) {
|
|
531
|
+
const context = {
|
|
532
|
+
depth: 0,
|
|
533
|
+
operatorCount: 0,
|
|
534
|
+
values: []
|
|
535
|
+
};
|
|
536
|
+
const text = `(${compileFilterRecursive(filter, context)})`;
|
|
537
|
+
if (text.length > MAX_QUERY_LENGTH) {
|
|
538
|
+
throw new RangeError(`Compiled filter too long: ${text.length} bytes (max: ${MAX_QUERY_LENGTH})`);
|
|
539
|
+
}
|
|
540
|
+
return createFragment(text, context.values);
|
|
541
|
+
}
|
|
542
|
+
export {
|
|
543
|
+
sql,
|
|
544
|
+
compileFilter
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
//# debugId=B02F5A27F175BBBF64756E2164756E21
|
|
548
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/constants.ts", "../src/fragment.ts", "../src/sql.ts", "../src/filter.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Common regex patterns and limits used across the codebase\n */\n\n// Maximum query length (100KB)\nexport const MAX_QUERY_LENGTH = 102400\n\n// Maximum length of a single identifier part (table/column/alias name).\n// Bounds the amount of attacker-influenced text that can be quoted into a\n// query, preventing identifier-based denial-of-service (huge field names).\nexport const MAX_IDENTIFIER_LENGTH = 255\n\n// Maximum length of a LIKE/ILIKE/REGEXP pattern. These patterns are evaluated\n// by the underlying SQLite engine at query time; bounding their size limits\n// pathological matching cost (e.g. catastrophic backtracking in a REGEXP\n// extension, or quadratic LIKE scans).\nexport const MAX_PATTERN_LENGTH = 1024\n\n// Regex to detect stacked queries (semicolon followed by non-whitespace).\n// Applied to a \"code-only\" view of the query (string literals and comments\n// stripped) so that semicolons inside literals/comments do not cause false\n// positives, and commented-out semicolons cannot smuggle a second statement.\nexport const STACKED_QUERY_REGEX = /;[\\s\\S]*\\S/\n\n// ANSI identifier validation - supports qualified identifiers (e.g., table.column)\nexport const QUALIFIED_IDENTIFIER_REGEX =\n /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$/\n\n// Simple identifier validation (for individual parts)\nexport const SIMPLE_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/\n",
|
|
6
|
+
"import type { SqlFragment } from './types'\n\n/**\n * Module-private registry of every SQL fragment minted by this library.\n *\n * Trust model: the text of a query is \"code\" (must originate from the\n * developer) and interpolated values are \"data\" (always parameterized). The\n * only way to contribute raw text is through a fragment created by one of the\n * library helpers (sql, sql.raw, sql.ident, sql.in, sql.blob, sql.join,\n * compileFilter). Membership in this WeakSet is the unforgeable proof that an\n * object is such a fragment.\n *\n * A plain object that merely looks like `{ text, values }` (e.g. from\n * JSON.parse or an untrusted request body) is NOT in this set, so it can never\n * be treated as raw SQL. This closes the structural duck-typing bypass.\n */\nconst fragmentRegistry = new WeakSet<object>()\n\n/**\n * Create and register an immutable, branded SQL fragment.\n *\n * The returned object is frozen (including its values array) so callers cannot\n * mutate a fragment after the integrity checks that produced it.\n */\nexport function createFragment(\n text: string,\n values: readonly unknown[],\n): SqlFragment {\n const fragment: SqlFragment = Object.freeze({\n text,\n values: Object.freeze([...values]),\n })\n\n fragmentRegistry.add(fragment)\n\n return fragment\n}\n\n/**\n * Type guard: was this value minted by the library (and therefore trusted to\n * contribute raw SQL text)?\n */\nexport function isSqlFragment(value: unknown): value is SqlFragment {\n return (\n typeof value === 'object' &&\n value !== null &&\n fragmentRegistry.has(value as object)\n )\n}\n",
|
|
7
|
+
"import {\n MAX_IDENTIFIER_LENGTH,\n MAX_QUERY_LENGTH,\n QUALIFIED_IDENTIFIER_REGEX,\n SIMPLE_IDENTIFIER_REGEX,\n STACKED_QUERY_REGEX,\n} from './constants'\nimport { createFragment, isSqlFragment } from './fragment'\nimport type { SqlFragment, SqlQuery, SqlValue } from './types'\n\n/**\n * Format a date for SQLite (YYYY-MM-DD HH:MM:SS)\n */\nfunction formatDate(date: Date): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n const hours = String(date.getHours()).padStart(2, '0')\n const minutes = String(date.getMinutes()).padStart(2, '0')\n const seconds = String(date.getSeconds()).padStart(2, '0')\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`\n}\n\n/**\n * Convert a value to its SQLite representation\n */\nfunction sqlValue(value: SqlValue): unknown {\n if (value === null || value === undefined) {\n return null\n }\n\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return value\n }\n\n if (value instanceof Date) {\n return formatDate(value)\n }\n\n if (value instanceof Buffer || value instanceof Uint8Array) {\n throw new TypeError(\n 'Buffer/Uint8Array values must be used with sql.blob() for safe BLOB handling',\n )\n }\n\n throw new TypeError(`Unsupported value type: ${typeof value}`)\n}\n\n/**\n * Quote a single identifier part\n */\nfunction quoteSingleIdentifier(identifier: string): string {\n if (identifier.length > MAX_IDENTIFIER_LENGTH) {\n throw new TypeError(\n `Identifier part too long: ${identifier.length} characters (max: ${MAX_IDENTIFIER_LENGTH})`,\n )\n }\n if (!SIMPLE_IDENTIFIER_REGEX.test(identifier)) {\n throw new TypeError(\n `Invalid identifier part: ${identifier}. Must be a valid ANSI identifier.`,\n )\n }\n return `\"${identifier}\"`\n}\n\n/**\n * Quote a qualified identifier by splitting on dots and quoting each part\n */\nfunction quoteQualifiedIdentifier(identifier: string): string {\n if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {\n throw new TypeError(\n `Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`,\n )\n }\n\n const parts = identifier.split('.')\n return parts.map(quoteSingleIdentifier).join('.')\n}\n\n/**\n * Validate and quote a SQL identifier or array of identifiers/fragments\n */\nfunction sqlIdent(\n identifier: string | readonly (string | SqlFragment)[],\n): SqlFragment {\n // Handle array of identifiers and fragments\n if (Array.isArray(identifier)) {\n if (identifier.length === 0) {\n throw new TypeError('Identifier array cannot be empty')\n }\n\n const fragments: SqlFragment[] = []\n\n for (const item of identifier) {\n // Only fragments minted by this library may pass through unquoted. This\n // prevents a forged `{ text, values }` object (e.g. from untrusted JSON)\n // from being injected as raw SQL via sql.ident().\n if (isSqlFragment(item)) {\n fragments.push(item)\n } else if (typeof item === 'string') {\n // Handle string identifiers\n if (!item) {\n throw new TypeError('All identifiers must be non-empty strings')\n }\n\n if (!QUALIFIED_IDENTIFIER_REGEX.test(item)) {\n throw new TypeError(\n `Invalid identifier: ${item}. Must be a valid identifier or qualified identifier (e.g., table.column)`,\n )\n }\n\n fragments.push(createFragment(quoteQualifiedIdentifier(item), []))\n } else {\n throw new TypeError('Array items must be strings or SQL fragments')\n }\n }\n\n // Join all fragments\n const text = fragments.map((f) => f.text).join(', ')\n const values = fragments.flatMap((f) => [...f.values])\n\n return createFragment(text, values)\n }\n\n // Handle single identifier (existing behavior)\n if (!identifier || typeof identifier !== 'string') {\n throw new TypeError('Identifier must be a non-empty string')\n }\n\n if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {\n throw new TypeError(\n `Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`,\n )\n }\n\n return createFragment(quoteQualifiedIdentifier(identifier), [])\n}\n\n/**\n * Create SQL IN clause from array\n */\nfunction sqlIn(array: readonly unknown[]): SqlFragment {\n if (!Array.isArray(array)) {\n throw new TypeError('sql.in() requires an array')\n }\n\n if (array.length === 0) {\n throw new TypeError('sql.in() cannot be used with empty arrays')\n }\n\n // Soft warning for large arrays\n if (array.length > 1000) {\n console.warn(\n `sql.in(): Large array with ${array.length} items. Consider using temporary tables for better performance.`,\n )\n }\n\n const placeholders = array.map(() => '?').join(',')\n const values = array.map(sqlValue)\n\n return createFragment(`(${placeholders})`, values)\n}\n\n/**\n * Create raw SQL fragment (DANGEROUS - must not contain user input).\n *\n * This is the library's single, explicit trust boundary: whatever string is\n * passed here becomes SQL verbatim. Only ever pass developer-authored,\n * constant SQL. Never pass user input. For dynamic WHERE clauses use\n * compileFilter(); for identifiers use sql.ident().\n */\nfunction sqlRaw(rawSql: string): SqlFragment {\n if (typeof rawSql !== 'string') {\n throw new TypeError('sql.raw() requires a string')\n }\n\n return createFragment(rawSql, [])\n}\n\n/**\n * Create SQL fragment for BLOB data (for validated binary data)\n */\nfunction sqlBlob(data: Buffer | Uint8Array): SqlFragment {\n if (!(data instanceof Buffer) && !(data instanceof Uint8Array)) {\n throw new TypeError('sql.blob() requires a Buffer or Uint8Array')\n }\n\n return createFragment('?', [data])\n}\n\n/**\n * Tokens that allow breaking out of a SQL expression context. A join separator\n * is structural SQL (a connector such as `, `, ` AND `, ` OR `). To make\n * arbitrary separators safe regardless of their origin, we forbid the\n * primitives that would let a separator escape the connector role: string and\n * identifier literal delimiters, statement terminators, comment markers, NUL,\n * and backslash escapes.\n */\nconst SEPARATOR_FORBIDDEN_TOKENS = [\n \"'\",\n '\"',\n '`',\n '[',\n ']',\n ';',\n '\\\\',\n '\\0',\n '--',\n '/*',\n '*/',\n] as const\n\n/**\n * Validate a string separator for sql.join(). Allows any structural connector\n * while rejecting the primitives used to inject literals, comments, or extra\n * statements. Parentheses must be balanced so a separator cannot escape the\n * grouping it sits within.\n */\nfunction assertSafeSeparator(separator: string): void {\n for (const token of SEPARATOR_FORBIDDEN_TOKENS) {\n if (separator.includes(token)) {\n throw new TypeError(\n `Unsafe sql.join() separator: contains forbidden token ${JSON.stringify(\n token,\n )}. Pass a SqlFragment (e.g. sql.raw) if you need parameterized separators.`,\n )\n }\n }\n\n let depth = 0\n for (const char of separator) {\n if (char === '(') {\n depth++\n } else if (char === ')') {\n depth--\n if (depth < 0) {\n throw new TypeError(\n 'Unsafe sql.join() separator: unbalanced parentheses',\n )\n }\n }\n }\n if (depth !== 0) {\n throw new TypeError('Unsafe sql.join() separator: unbalanced parentheses')\n }\n}\n\n/**\n * Join SQL fragments with a separator.\n *\n * Fragments must be library-minted (branded) fragments. The separator may be:\n * - a string: treated as a structural connector and validated by\n * assertSafeSeparator() so any connector is supported safely; or\n * - a SqlFragment: its text becomes the connector and its values are\n * interleaved between fragments, allowing fully parameterized separators.\n */\nfunction sqlJoin(\n fragments: readonly SqlFragment[],\n separator: string | SqlFragment = ', ',\n): SqlFragment {\n if (!Array.isArray(fragments)) {\n throw new TypeError('sql.join() requires an array of fragments')\n }\n\n for (const fragment of fragments) {\n if (!isSqlFragment(fragment)) {\n throw new TypeError(\n 'sql.join() requires SQL fragments created by the sql tag or its helpers',\n )\n }\n }\n\n if (fragments.length === 0) {\n return createFragment('', [])\n }\n\n let separatorText: string\n let separatorValues: readonly unknown[] = []\n\n if (isSqlFragment(separator)) {\n separatorText = separator.text\n separatorValues = separator.values\n } else if (typeof separator === 'string') {\n assertSafeSeparator(separator)\n separatorText = separator\n } else {\n throw new TypeError(\n 'sql.join() separator must be a string or a SQL fragment',\n )\n }\n\n let text = ''\n const values: unknown[] = []\n\n fragments.forEach((fragment, index) => {\n if (index > 0) {\n text += separatorText\n values.push(...separatorValues)\n }\n text += fragment.text\n values.push(...fragment.values)\n })\n\n return createFragment(text, values)\n}\n\n/**\n * Scan assembled SQL once to (a) count true placeholders and (b) produce a\n * \"code-only\" view with string/identifier literals and comments removed.\n *\n * Placeholders inside literals/comments are not counted, and semicolons inside\n * literals/comments are not treated as statement separators.\n */\nfunction scanSql(text: string): { placeholderCount: number; code: string } {\n let placeholderCount = 0\n let code = ''\n let i = 0\n const length = text.length\n\n while (i < length) {\n const char = text[i]\n const next = text[i + 1]\n\n // Line comment: -- ... <newline>\n if (char === '-' && next === '-') {\n i += 2\n while (i < length && text[i] !== '\\n') {\n i++\n }\n continue\n }\n\n // Block comment: /* ... */\n if (char === '/' && next === '*') {\n i += 2\n while (i < length && !(text[i] === '*' && text[i + 1] === '/')) {\n i++\n }\n i += 2\n continue\n }\n\n // Quoted string ('...') or quoted identifier (\"...\", `...`), with the SQL\n // convention that the quote char is escaped by doubling it.\n if (char === \"'\" || char === '\"' || char === '`') {\n const quote = char\n i++\n while (i < length) {\n if (text[i] === quote) {\n if (text[i + 1] === quote) {\n i += 2\n continue\n }\n i++\n break\n }\n i++\n }\n continue\n }\n\n // Bracket-quoted identifier: [ ... ]\n if (char === '[') {\n i++\n while (i < length && text[i] !== ']') {\n i++\n }\n i++\n continue\n }\n\n if (char === '?') {\n placeholderCount++\n }\n code += char\n i++\n }\n\n return { placeholderCount, code }\n}\n\n/**\n * Main SQL tagged template function\n */\nfunction sql(strings: TemplateStringsArray, ...values: unknown[]): SqlQuery {\n // Build the query text and collect values\n let text = strings[0] || ''\n const queryValues: unknown[] = []\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i]\n\n // Only library-minted fragments contribute raw text; everything else is\n // parameterized. This blocks forged `{ text, values }` objects.\n if (isSqlFragment(value)) {\n text += value.text\n queryValues.push(...value.values)\n } else {\n // Regular value - add placeholder and collect value\n text += '?'\n queryValues.push(sqlValue(value as SqlValue))\n }\n\n text += strings[i + 1] || ''\n }\n\n // Security checks\n if (text.length > MAX_QUERY_LENGTH) {\n throw new Error(\n `Query too long: ${text.length} bytes (max: ${MAX_QUERY_LENGTH})`,\n )\n }\n\n const { placeholderCount, code } = scanSql(text)\n\n // Integrity: every placeholder must have exactly one bound value and vice\n // versa. Catches raw fragments that smuggle a stray `?` (or, conversely,\n // raw SQL that forgot to carry its values), keeping text and values aligned.\n if (placeholderCount !== queryValues.length) {\n throw new Error(\n `Placeholder count (${placeholderCount}) does not match bound value count (${queryValues.length}). ` +\n 'Did a raw fragment contain a \"?\" without supplying its value?',\n )\n }\n\n if (STACKED_QUERY_REGEX.test(code)) {\n throw new Error('Stacked queries are not allowed')\n }\n\n // Return frozen, branded result so it can be safely composed into other\n // queries (e.g. via sql.join) without being mistaken for a forgery.\n return createFragment(text, queryValues) as SqlQuery\n}\n\n// Attach helper functions to sql\nsql.value = sqlValue\nsql.ident = sqlIdent\nsql.in = sqlIn\nsql.raw = sqlRaw\nsql.blob = sqlBlob\nsql.join = sqlJoin\n\nexport { sql }\n",
|
|
8
|
+
"import {\n MAX_IDENTIFIER_LENGTH,\n MAX_PATTERN_LENGTH,\n MAX_QUERY_LENGTH,\n SIMPLE_IDENTIFIER_REGEX,\n} from './constants'\nimport { createFragment } from './fragment'\nimport { sql } from './sql'\nimport type {\n ComparisonOperators,\n FieldCondition,\n FilterResult,\n JsonFilter,\n} from './types'\n\n// Limits to prevent DoS attacks\nconst MAX_NESTING_DEPTH = 10\nconst MAX_OPERATORS = 100\n\n// Valid operators for validation\nconst VALID_OPERATORS = new Set([\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'ne',\n 'in',\n 'nin',\n 'like',\n 'ilike',\n 'regex',\n 'exists',\n 'and',\n 'or',\n])\n\n/**\n * Context for tracking compilation state\n */\ninterface CompileContext {\n depth: number\n operatorCount: number\n values: unknown[]\n}\n\n/**\n * Check if a field name represents a JSON path (contains dots)\n */\nfunction isJsonPath(field: string): boolean {\n return field.includes('.')\n}\n\n/**\n * Convert a JSON path field to json_extract expression\n */\nfunction compileJsonPath(field: string): {\n columnName: string\n jsonPath: string\n} {\n const parts = field.split('.')\n const columnName = parts[0]\n const jsonPath = parts.slice(1)\n\n if (!columnName || jsonPath.length === 0 || jsonPath.some((part) => !part)) {\n throw new SyntaxError(`Invalid JSON path: ${field}`)\n }\n\n return {\n columnName,\n jsonPath: '$.' + jsonPath.join('.'),\n }\n}\n\n/**\n * Validate if a string is a valid SQL identifier\n */\nfunction isValidSqlIdentifier(identifier: string): boolean {\n return (\n identifier.length <= MAX_IDENTIFIER_LENGTH &&\n SIMPLE_IDENTIFIER_REGEX.test(identifier)\n )\n}\n\n/**\n * Validate a LIKE/ILIKE/REGEXP pattern, bounding its length to limit\n * pathological matching cost at the SQLite layer.\n */\nfunction assertPatternWithinLimit(operator: string, pattern: string): void {\n if (pattern.length > MAX_PATTERN_LENGTH) {\n throw new RangeError(\n `${operator} pattern too long: ${pattern.length} characters (max: ${MAX_PATTERN_LENGTH})`,\n )\n }\n}\n\n/**\n * Compile a single field condition to SQL\n */\nfunction compileFieldCondition(\n field: string,\n condition: FieldCondition,\n context: CompileContext,\n alias?: string,\n): string {\n // Handle direct value (equality)\n if (\n condition === null ||\n condition === undefined ||\n typeof condition === 'string' ||\n typeof condition === 'number' ||\n typeof condition === 'boolean' ||\n condition instanceof Date\n ) {\n context.operatorCount++\n if (context.operatorCount > MAX_OPERATORS) {\n throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`)\n }\n\n if (isJsonPath(field)) {\n const { columnName, jsonPath } = compileJsonPath(field)\n const identFragment = sql.ident(columnName)\n const fullFieldExpr = alias\n ? `${alias}.${identFragment.text}`\n : identFragment.text\n\n if (condition === null || condition === undefined) {\n context.values.push(jsonPath)\n return `(json_extract(${fullFieldExpr}, ?) IS NULL)`\n } else {\n context.values.push(jsonPath, sql.value(condition))\n return `(json_extract(${fullFieldExpr}, ?) = ?)`\n }\n } else {\n const identFragment = sql.ident(field)\n const fullFieldExpr = alias\n ? `${alias}.${identFragment.text}`\n : identFragment.text\n\n if (condition === null || condition === undefined) {\n return `(${fullFieldExpr} IS NULL)`\n } else {\n context.values.push(sql.value(condition))\n return `(${fullFieldExpr} = ?)`\n }\n }\n }\n\n // Handle operator object\n if (typeof condition !== 'object' || condition === null) {\n throw new TypeError('Condition must be a value or operator object')\n }\n\n const operators = condition as ComparisonOperators\n const clauses: string[] = []\n\n // Validate all operators are known\n for (const op of Object.keys(operators)) {\n if (!VALID_OPERATORS.has(op)) {\n throw new SyntaxError(`Unknown operator: ${op}`)\n }\n }\n\n const identFragment = sql.ident(\n isJsonPath(field) ? compileJsonPath(field).columnName : field,\n )\n const baseFieldExpr = alias\n ? `${alias}.${identFragment.text}`\n : identFragment.text\n const fieldExpr = isJsonPath(field)\n ? `json_extract(${baseFieldExpr}, ?)`\n : baseFieldExpr\n\n // Add JSON path to values if needed\n if (isJsonPath(field)) {\n context.values.push(compileJsonPath(field).jsonPath)\n }\n\n // Handle exists operator first (it overrides other operators)\n if ('exists' in operators) {\n context.operatorCount++\n if (context.operatorCount > MAX_OPERATORS) {\n throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`)\n }\n\n return operators.exists\n ? `(${fieldExpr} IS NOT NULL)`\n : `(${fieldExpr} IS NULL)`\n }\n\n // Handle comparison operators\n for (const [op, value] of Object.entries(operators)) {\n context.operatorCount++\n if (context.operatorCount > MAX_OPERATORS) {\n throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`)\n }\n\n switch (op) {\n case 'gt':\n context.values.push(sql.value(value))\n clauses.push(`${fieldExpr} > ?`)\n break\n case 'gte':\n context.values.push(sql.value(value))\n clauses.push(`${fieldExpr} >= ?`)\n break\n case 'lt':\n context.values.push(sql.value(value))\n clauses.push(`${fieldExpr} < ?`)\n break\n case 'lte':\n context.values.push(sql.value(value))\n clauses.push(`${fieldExpr} <= ?`)\n break\n case 'ne':\n if (value === null || value === undefined) {\n clauses.push(`${fieldExpr} IS NOT NULL`)\n } else {\n context.values.push(sql.value(value))\n clauses.push(`${fieldExpr} <> ?`)\n }\n break\n case 'in': {\n if (!Array.isArray(value)) {\n throw new TypeError('IN operator requires an array')\n }\n if (value.length === 0) {\n throw new TypeError('IN operator cannot be used with empty arrays')\n }\n if (value.length > 999) {\n throw new RangeError(\n 'IN operator cannot be used with arrays larger than 999 items',\n )\n }\n\n const inFragment = sql.in(value)\n context.values.push(...inFragment.values)\n clauses.push(`${fieldExpr} IN ${inFragment.text}`)\n break\n }\n case 'nin': {\n if (!Array.isArray(value)) {\n throw new TypeError('NIN operator requires an array')\n }\n if (value.length === 0) {\n throw new TypeError('NIN operator cannot be used with empty arrays')\n }\n if (value.length > 999) {\n throw new RangeError(\n 'NIN operator cannot be used with arrays larger than 999 items',\n )\n }\n\n const ninFragment = sql.in(value)\n context.values.push(...ninFragment.values)\n clauses.push(`${fieldExpr} NOT IN ${ninFragment.text}`)\n break\n }\n case 'like':\n if (typeof value !== 'string') {\n throw new TypeError('LIKE operator requires a string pattern')\n }\n assertPatternWithinLimit('LIKE', value)\n context.values.push(value)\n clauses.push(`${fieldExpr} LIKE ?`)\n break\n case 'ilike':\n if (typeof value !== 'string') {\n throw new TypeError('ILIKE operator requires a string pattern')\n }\n assertPatternWithinLimit('ILIKE', value)\n context.values.push(value)\n clauses.push(`${fieldExpr} LIKE ? COLLATE NOCASE`)\n break\n case 'regex':\n if (typeof value !== 'string') {\n throw new TypeError('REGEX operator requires a string pattern')\n }\n assertPatternWithinLimit('REGEX', value)\n context.values.push(value)\n clauses.push(`${fieldExpr} REGEXP ?`)\n break\n }\n }\n\n if (clauses.length === 0) {\n throw new SyntaxError(\n 'Operator object must contain at least one valid operator',\n )\n }\n\n return clauses.length === 1 ? `(${clauses[0]})` : `(${clauses.join(' AND ')})`\n}\n\n/**\n * Compile a JSON filter to SQL recursively\n */\nfunction compileFilterRecursive(\n filter: JsonFilter,\n context: CompileContext,\n alias?: string,\n): string {\n if (context.depth >= MAX_NESTING_DEPTH) {\n throw new RangeError(`Nesting depth too deep (max: ${MAX_NESTING_DEPTH})`)\n }\n\n if (typeof filter !== 'object' || filter === null) {\n throw new TypeError('Filter must be an object')\n }\n\n context.depth++\n const clauses: string[] = []\n\n // Handle logical operators first\n if ('and' in filter && filter.and) {\n context.operatorCount++\n if (context.operatorCount > MAX_OPERATORS) {\n throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`)\n }\n\n if (!Array.isArray(filter.and)) {\n throw new TypeError('AND operator must be an array')\n }\n if (filter.and.length === 0) {\n throw new TypeError('AND operator cannot be used with empty arrays')\n }\n\n const andClauses = filter.and.map((subFilter) =>\n compileFilterRecursive(subFilter, context, alias),\n )\n clauses.push(`(${andClauses.join(' AND ')})`)\n }\n\n if ('or' in filter && filter.or) {\n context.operatorCount++\n if (context.operatorCount > MAX_OPERATORS) {\n throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`)\n }\n\n if (!Array.isArray(filter.or)) {\n throw new TypeError('OR operator must be an array')\n }\n if (filter.or.length === 0) {\n throw new TypeError('OR operator cannot be used with empty arrays')\n }\n\n const orClauses = filter.or.map((subFilter) =>\n compileFilterRecursive(subFilter, context, alias),\n )\n clauses.push(`(${orClauses.join(' OR ')})`)\n }\n\n // Handle field conditions (implicit AND) first - preserve original order\n const fieldEntries = Object.entries(filter).filter(\n ([field, condition]) =>\n field !== 'and' &&\n field !== 'or' &&\n !field.startsWith('$') &&\n condition !== undefined,\n )\n\n for (const [field, condition] of fieldEntries) {\n // Handle array conditions (for 'and'/'or' at field level)\n if (Array.isArray(condition)) {\n throw new SyntaxError(\n `Field '${field}' cannot have array value. Use logical operators 'and'/'or' instead.`,\n )\n }\n\n clauses.push(\n compileFieldCondition(field, condition as FieldCondition, context, alias),\n )\n }\n\n // Handle alias blocks (keys starting with $) after regular fields\n const aliasEntries = Object.entries(filter).filter(\n ([key, value]) =>\n key.startsWith('$') &&\n value !== undefined &&\n typeof value === 'object' &&\n value !== null,\n )\n\n for (const [aliasKey, aliasFilter] of aliasEntries) {\n const aliasName = aliasKey.slice(1) // Remove the $ prefix\n\n // Validate alias name\n if (!isValidSqlIdentifier(aliasName)) {\n throw new SyntaxError(`Invalid alias identifier: ${aliasName}`)\n }\n\n // Recursively compile the alias block with the alias context\n const aliasClause = compileFilterRecursive(\n aliasFilter as JsonFilter,\n context,\n aliasName,\n )\n clauses.push(aliasClause)\n }\n\n context.depth--\n\n if (clauses.length === 0) {\n throw new SyntaxError('Filter must contain at least one condition')\n }\n\n // Fix: ensure we have at least one clause before accessing clauses[0]\n if (clauses.length === 1) {\n return clauses[0]!\n } else {\n return `(${clauses.join(' AND ')})`\n }\n}\n\n/**\n * Compile a JSON filter to a SQL WHERE clause\n */\nexport function compileFilter(filter: JsonFilter): FilterResult {\n const context: CompileContext = {\n depth: 0,\n operatorCount: 0,\n values: [],\n }\n\n const text = `(${compileFilterRecursive(filter, context)})`\n\n // Backstop against output-size denial-of-service. Identifier-length and\n // operator-count limits keep individual pieces bounded; this guards the\n // aggregate so compileFilter() cannot emit an oversized clause even when used\n // standalone (the sql tag enforces the same cap when fragments are composed).\n if (text.length > MAX_QUERY_LENGTH) {\n throw new RangeError(\n `Compiled filter too long: ${text.length} bytes (max: ${MAX_QUERY_LENGTH})`,\n )\n }\n\n // Returned as a branded fragment so it can be interpolated directly into a\n // sql`` template with its values automatically collected — no sql.raw()\n // needed, which removes the value-binding footgun.\n return createFragment(text, context.values) as FilterResult\n}\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "B02F5A27F175BBBF64756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
package/dist/sql.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SqlFragment, SqlQuery, SqlValue } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a value to its SQLite representation
|
|
4
|
+
*/
|
|
5
|
+
declare function sqlValue(value: SqlValue): unknown;
|
|
6
|
+
/**
|
|
7
|
+
* Validate and quote a SQL identifier or array of identifiers/fragments
|
|
8
|
+
*/
|
|
9
|
+
declare function sqlIdent(identifier: string | readonly (string | SqlFragment)[]): SqlFragment;
|
|
10
|
+
/**
|
|
11
|
+
* Create SQL IN clause from array
|
|
12
|
+
*/
|
|
13
|
+
declare function sqlIn(array: readonly unknown[]): SqlFragment;
|
|
14
|
+
/**
|
|
15
|
+
* Create raw SQL fragment (DANGEROUS - must not contain user input).
|
|
16
|
+
*
|
|
17
|
+
* This is the library's single, explicit trust boundary: whatever string is
|
|
18
|
+
* passed here becomes SQL verbatim. Only ever pass developer-authored,
|
|
19
|
+
* constant SQL. Never pass user input. For dynamic WHERE clauses use
|
|
20
|
+
* compileFilter(); for identifiers use sql.ident().
|
|
21
|
+
*/
|
|
22
|
+
declare function sqlRaw(rawSql: string): SqlFragment;
|
|
23
|
+
/**
|
|
24
|
+
* Create SQL fragment for BLOB data (for validated binary data)
|
|
25
|
+
*/
|
|
26
|
+
declare function sqlBlob(data: Buffer | Uint8Array): SqlFragment;
|
|
27
|
+
/**
|
|
28
|
+
* Join SQL fragments with a separator.
|
|
29
|
+
*
|
|
30
|
+
* Fragments must be library-minted (branded) fragments. The separator may be:
|
|
31
|
+
* - a string: treated as a structural connector and validated by
|
|
32
|
+
* assertSafeSeparator() so any connector is supported safely; or
|
|
33
|
+
* - a SqlFragment: its text becomes the connector and its values are
|
|
34
|
+
* interleaved between fragments, allowing fully parameterized separators.
|
|
35
|
+
*/
|
|
36
|
+
declare function sqlJoin(fragments: readonly SqlFragment[], separator?: string | SqlFragment): SqlFragment;
|
|
37
|
+
/**
|
|
38
|
+
* Main SQL tagged template function
|
|
39
|
+
*/
|
|
40
|
+
declare function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlQuery;
|
|
41
|
+
declare namespace sql {
|
|
42
|
+
export var value: typeof sqlValue;
|
|
43
|
+
export var ident: typeof sqlIdent;
|
|
44
|
+
var _a: typeof sqlIn;
|
|
45
|
+
export var raw: typeof sqlRaw;
|
|
46
|
+
export var blob: typeof sqlBlob;
|
|
47
|
+
export var join: typeof sqlJoin;
|
|
48
|
+
export { _a as in };
|
|
49
|
+
}
|
|
50
|
+
export { sql };
|
|
51
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAgB9D;;GAEG;AACH,iBAAS,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAwB1C;AAiCD;;GAEG;AACH,iBAAS,QAAQ,CACf,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC,EAAE,GACrD,WAAW,CAoDb;AAED;;GAEG;AACH,iBAAS,KAAK,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,WAAW,CAoBrD;AAED;;;;;;;GAOG;AACH,iBAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAM3C;AAED;;GAEG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,CAMvD;AA2DD;;;;;;;;GAQG;AACH,iBAAS,OAAO,CACd,SAAS,EAAE,SAAS,WAAW,EAAE,EACjC,SAAS,GAAE,MAAM,GAAG,WAAkB,GACrC,WAAW,CA6Cb;AA6ED;;GAEG;AACH,iBAAS,GAAG,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAgD1E;kBAhDQ,GAAG;;;;;;;;;AA0DZ,OAAO,EAAE,GAAG,EAAE,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The result of a SQL tagged template
|
|
3
|
+
*/
|
|
4
|
+
export interface SqlQuery {
|
|
5
|
+
readonly text: string;
|
|
6
|
+
readonly values: readonly unknown[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Valid SQL value types that can be safely interpolated
|
|
10
|
+
*/
|
|
11
|
+
export type SqlValue = string | number | boolean | null | undefined | Date | Buffer | Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* A SQL fragment that can be joined with other fragments
|
|
14
|
+
*/
|
|
15
|
+
export interface SqlFragment {
|
|
16
|
+
readonly text: string;
|
|
17
|
+
readonly values: readonly unknown[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Comparison operators for JSON filters
|
|
21
|
+
*/
|
|
22
|
+
export interface ComparisonOperators {
|
|
23
|
+
/** Greater than */
|
|
24
|
+
gt?: SqlValue;
|
|
25
|
+
/** Greater than or equal */
|
|
26
|
+
gte?: SqlValue;
|
|
27
|
+
/** Less than */
|
|
28
|
+
lt?: SqlValue;
|
|
29
|
+
/** Less than or equal */
|
|
30
|
+
lte?: SqlValue;
|
|
31
|
+
/** Not equal */
|
|
32
|
+
ne?: SqlValue;
|
|
33
|
+
/** IN array */
|
|
34
|
+
in?: readonly SqlValue[];
|
|
35
|
+
/** NOT IN array */
|
|
36
|
+
nin?: readonly SqlValue[];
|
|
37
|
+
/** LIKE pattern */
|
|
38
|
+
like?: string;
|
|
39
|
+
/** Case-insensitive LIKE */
|
|
40
|
+
ilike?: string;
|
|
41
|
+
/** Regular expression pattern */
|
|
42
|
+
regex?: string;
|
|
43
|
+
/** Field exists check (true = IS NOT NULL, false = IS NULL) */
|
|
44
|
+
exists?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A field condition can be a direct value or an object with operators
|
|
48
|
+
*/
|
|
49
|
+
export type FieldCondition = SqlValue | ComparisonOperators;
|
|
50
|
+
/**
|
|
51
|
+
* Logical operators for combining filters
|
|
52
|
+
*/
|
|
53
|
+
export interface LogicalOperators {
|
|
54
|
+
/** All conditions must match */
|
|
55
|
+
and?: readonly JsonFilter[];
|
|
56
|
+
/** Any condition must match */
|
|
57
|
+
or?: readonly JsonFilter[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A JSON filter for compiling to SQL WHERE clauses
|
|
61
|
+
* Can be a field-to-condition mapping with implicit AND, explicit logical operators,
|
|
62
|
+
* or alias blocks (keys starting with $) containing nested filters
|
|
63
|
+
*/
|
|
64
|
+
export type JsonFilter = LogicalOperators & {
|
|
65
|
+
[field: string]: FieldCondition | readonly JsonFilter[] | JsonFilter | undefined;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Result of compiling a JSON filter to SQL
|
|
69
|
+
*/
|
|
70
|
+
export interface FilterResult {
|
|
71
|
+
readonly text: string;
|
|
72
|
+
readonly values: readonly unknown[];
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;CACpC;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAChB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,IAAI,GACJ,MAAM,GACN,UAAU,CAAA;AAEd;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,mBAAmB;IACnB,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb,4BAA4B;IAC5B,GAAG,CAAC,EAAE,QAAQ,CAAA;IACd,gBAAgB;IAChB,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb,yBAAyB;IACzB,GAAG,CAAC,EAAE,QAAQ,CAAA;IACd,gBAAgB;IAChB,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb,eAAe;IACf,EAAE,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;IACxB,mBAAmB;IACnB,GAAG,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;IACzB,mBAAmB;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,mBAAmB,CAAA;AAE3D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,GAAG,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;IAC3B,+BAA+B;IAC/B,EAAE,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG;IAC1C,CAAC,KAAK,EAAE,MAAM,GACV,cAAc,GACd,SAAS,UAAU,EAAE,GACrB,UAAU,GACV,SAAS,CAAA;CACd,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;CACpC"}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@truto/sqlite-builder","version":"2.0.2-canary.
|
|
1
|
+
{"name":"@truto/sqlite-builder","version":"2.0.2-canary.3","description":"debug canary","license":"MIT","main":"dist/index.js"}
|
package/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
`
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = {};
|