sql-love 1.1.2 → 1.1.5
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/README.md +9 -6
- package/dist/index.cjs +19 -9
- package/dist/index.d.ts +7 -4
- package/dist/index.mjs +19 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<img alt="SQL Love Logo" src="https://github.com/kensnyder/sql-love/raw/main/assets/sql-love-logo.png?v=1.1.
|
|
1
|
+
<img alt="SQL Love Logo" src="https://github.com/kensnyder/sql-love/raw/main/assets/sql-love-logo.png?v=1.1.5" width="250" height="208" />
|
|
2
2
|
|
|
3
3
|
# sql-love
|
|
4
4
|
|
|
5
|
-
[](https://github.com/search?q=repo:kensnyder/sql-love++language:TypeScript&type=code)
|
|
6
|
+
[](https://codecov.io/gh/kensnyder/sql-love)
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|

|
|
10
|
-
[](https://opensource.org/licenses/ISC)
|
|
11
11
|
|
|
12
12
|
Classes for parsing and building SQL select queries in Node/Bun/Deno.
|
|
13
13
|
|
|
@@ -189,7 +189,7 @@ Use the following methods to build queries. And see
|
|
|
189
189
|
- `query.having(columnValueRecord)` - Add multiple conditions
|
|
190
190
|
- `query.orHaving(expressions)` - Multiple `having()`s joined by OR
|
|
191
191
|
- `query.orderBy(column, direction)` - Add ORDER BY clause
|
|
192
|
-
- `query.sortField(
|
|
192
|
+
- `query.sortField(field, mapNames, modifier)` - Add ORDER BY clause for an allowed field in mapNames
|
|
193
193
|
- `query.limit(num)` - Limit by the given number
|
|
194
194
|
- `query.offset(num)` - Specify an offset
|
|
195
195
|
- `query.page(num)` - Automatically calculate offset based on limit and page
|
|
@@ -316,8 +316,11 @@ query.orderBy('modified_at DESC NULLS LAST'); // ORDER BY modified_at DESC NULLS
|
|
|
316
316
|
query.orderBy('modified_at USING >'); // ORDER BY modified_at USING >
|
|
317
317
|
|
|
318
318
|
// Sorting with support for hyphens and mapping
|
|
319
|
-
|
|
319
|
+
// Only fields present in mapNames are allowed, so field names may safely
|
|
320
|
+
// come from user input (e.g. a "?sort=-created" query string param).
|
|
321
|
+
// Unknown fields emit a console.warn and add no ORDER BY clause.
|
|
320
322
|
query.sortField('-created', { created: 'created_at' }); // ORDER BY created_at DESC
|
|
323
|
+
query.sortField('-foo', { created: 'created_at' }); // console.warn; no ORDER BY added
|
|
321
324
|
query.sortField(
|
|
322
325
|
'-created_at',
|
|
323
326
|
{
|
package/dist/index.cjs
CHANGED
|
@@ -406,9 +406,10 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
406
406
|
*/
|
|
407
407
|
constructor(sql, bindings = {}) {
|
|
408
408
|
if (sql) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const
|
|
409
|
+
const keys = bindings ? Object.keys(bindings) : [];
|
|
410
|
+
if (keys.length > 0) {
|
|
411
|
+
const names = keys.sort((a, b) => b.length - a.length).map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
412
|
+
const regexp = new RegExp(`(?<!:):(${names})\\b`, "g");
|
|
412
413
|
sql = sql.replace(regexp, ($0, $1) => {
|
|
413
414
|
this._bindings.push(bindings[$1]);
|
|
414
415
|
return `__BOUND_${this._bindings.length - 1}__`;
|
|
@@ -859,7 +860,7 @@ UNION
|
|
|
859
860
|
if (numArgs === 1) {
|
|
860
861
|
collection.push(column);
|
|
861
862
|
return this;
|
|
862
|
-
} else if (numArgs === 2 && Array.isArray(operator) &&
|
|
863
|
+
} else if (numArgs === 2 && Array.isArray(operator) && substrCount(column, "?") === operator.length) {
|
|
863
864
|
const values = operator;
|
|
864
865
|
const sql = column.replace(/\?/g, () => {
|
|
865
866
|
this._bindings.push(values.shift());
|
|
@@ -1035,13 +1036,16 @@ UNION
|
|
|
1035
1036
|
return this;
|
|
1036
1037
|
}
|
|
1037
1038
|
/**
|
|
1038
|
-
* Sort by the given
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1039
|
+
* Sort by the given field, with a map of allowed fields to column expressions.
|
|
1040
|
+
* Only field names present in mapNames are allowed; anything else emits a
|
|
1041
|
+
* console.warn and adds no ORDER BY clause. This prevents SQL injection when
|
|
1042
|
+
* fieldName comes from user input.
|
|
1043
|
+
* @param fieldName The field name such as 'created' or '-created' for descending
|
|
1044
|
+
* @param mapNames Allowed field names mapped to column expressions
|
|
1041
1045
|
* @param [modifier] Additional clause to append such as 'NULLS LAST' or 'COLLATE "en_US"'
|
|
1042
1046
|
* @example
|
|
1043
|
-
* query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
1044
1047
|
* query.sortField('-created', {'created': 'created_at'}); // ORDER BY created_at DESC
|
|
1048
|
+
* query.sortField('-foo', {'created': 'created_at'}); // console.warn; no ORDER BY added
|
|
1045
1049
|
* query.sortField('-created', {
|
|
1046
1050
|
* 'created': 'created_at NULLS LAST',
|
|
1047
1051
|
* '-created': 'created_at DESC NULLS LAST'
|
|
@@ -1064,7 +1068,13 @@ UNION
|
|
|
1064
1068
|
direction = "DESC";
|
|
1065
1069
|
fieldName = fieldName.slice(1);
|
|
1066
1070
|
}
|
|
1067
|
-
|
|
1071
|
+
if (!Object.prototype.hasOwnProperty.call(mapNames, fieldName)) {
|
|
1072
|
+
console.warn(
|
|
1073
|
+
`SelectBuilder: sortField() ignoring unknown field "${fieldName.slice(0, 100)}"`
|
|
1074
|
+
);
|
|
1075
|
+
return this;
|
|
1076
|
+
}
|
|
1077
|
+
const column = mapNames[fieldName];
|
|
1068
1078
|
if (modifier) {
|
|
1069
1079
|
direction += ` ${modifier}`;
|
|
1070
1080
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -359,13 +359,16 @@ export declare class SelectBuilder {
|
|
|
359
359
|
*/
|
|
360
360
|
orderBy(column: string, direction?: "ASC" | "DESC"): this;
|
|
361
361
|
/**
|
|
362
|
-
* Sort by the given
|
|
363
|
-
*
|
|
364
|
-
*
|
|
362
|
+
* Sort by the given field, with a map of allowed fields to column expressions.
|
|
363
|
+
* Only field names present in mapNames are allowed; anything else emits a
|
|
364
|
+
* console.warn and adds no ORDER BY clause. This prevents SQL injection when
|
|
365
|
+
* fieldName comes from user input.
|
|
366
|
+
* @param fieldName The field name such as 'created' or '-created' for descending
|
|
367
|
+
* @param mapNames Allowed field names mapped to column expressions
|
|
365
368
|
* @param [modifier] Additional clause to append such as 'NULLS LAST' or 'COLLATE "en_US"'
|
|
366
369
|
* @example
|
|
367
|
-
* query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
368
370
|
* query.sortField('-created', {'created': 'created_at'}); // ORDER BY created_at DESC
|
|
371
|
+
* query.sortField('-foo', {'created': 'created_at'}); // console.warn; no ORDER BY added
|
|
369
372
|
* query.sortField('-created', {
|
|
370
373
|
* 'created': 'created_at NULLS LAST',
|
|
371
374
|
* '-created': 'created_at DESC NULLS LAST'
|
package/dist/index.mjs
CHANGED
|
@@ -366,9 +366,10 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
366
366
|
*/
|
|
367
367
|
constructor(sql, bindings = {}) {
|
|
368
368
|
if (sql) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
369
|
+
const keys = bindings ? Object.keys(bindings) : [];
|
|
370
|
+
if (keys.length > 0) {
|
|
371
|
+
const names = keys.sort((a, b) => b.length - a.length).map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
372
|
+
const regexp = new RegExp(`(?<!:):(${names})\\b`, "g");
|
|
372
373
|
sql = sql.replace(regexp, ($0, $1) => {
|
|
373
374
|
this._bindings.push(bindings[$1]);
|
|
374
375
|
return `__BOUND_${this._bindings.length - 1}__`;
|
|
@@ -819,7 +820,7 @@ UNION
|
|
|
819
820
|
if (numArgs === 1) {
|
|
820
821
|
collection.push(column);
|
|
821
822
|
return this;
|
|
822
|
-
} else if (numArgs === 2 && Array.isArray(operator) &&
|
|
823
|
+
} else if (numArgs === 2 && Array.isArray(operator) && substrCount(column, "?") === operator.length) {
|
|
823
824
|
const values = operator;
|
|
824
825
|
const sql = column.replace(/\?/g, () => {
|
|
825
826
|
this._bindings.push(values.shift());
|
|
@@ -995,13 +996,16 @@ UNION
|
|
|
995
996
|
return this;
|
|
996
997
|
}
|
|
997
998
|
/**
|
|
998
|
-
* Sort by the given
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
999
|
+
* Sort by the given field, with a map of allowed fields to column expressions.
|
|
1000
|
+
* Only field names present in mapNames are allowed; anything else emits a
|
|
1001
|
+
* console.warn and adds no ORDER BY clause. This prevents SQL injection when
|
|
1002
|
+
* fieldName comes from user input.
|
|
1003
|
+
* @param fieldName The field name such as 'created' or '-created' for descending
|
|
1004
|
+
* @param mapNames Allowed field names mapped to column expressions
|
|
1001
1005
|
* @param [modifier] Additional clause to append such as 'NULLS LAST' or 'COLLATE "en_US"'
|
|
1002
1006
|
* @example
|
|
1003
|
-
* query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
1004
1007
|
* query.sortField('-created', {'created': 'created_at'}); // ORDER BY created_at DESC
|
|
1008
|
+
* query.sortField('-foo', {'created': 'created_at'}); // console.warn; no ORDER BY added
|
|
1005
1009
|
* query.sortField('-created', {
|
|
1006
1010
|
* 'created': 'created_at NULLS LAST',
|
|
1007
1011
|
* '-created': 'created_at DESC NULLS LAST'
|
|
@@ -1024,7 +1028,13 @@ UNION
|
|
|
1024
1028
|
direction = "DESC";
|
|
1025
1029
|
fieldName = fieldName.slice(1);
|
|
1026
1030
|
}
|
|
1027
|
-
|
|
1031
|
+
if (!Object.prototype.hasOwnProperty.call(mapNames, fieldName)) {
|
|
1032
|
+
console.warn(
|
|
1033
|
+
`SelectBuilder: sortField() ignoring unknown field "${fieldName.slice(0, 100)}"`
|
|
1034
|
+
);
|
|
1035
|
+
return this;
|
|
1036
|
+
}
|
|
1037
|
+
const column = mapNames[fieldName];
|
|
1028
1038
|
if (modifier) {
|
|
1029
1039
|
direction += ` ${modifier}`;
|
|
1030
1040
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sql-love",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Build SQL queries with JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@types/bun": "latest",
|
|
56
56
|
"@types/node": "latest",
|
|
57
57
|
"dts-bundle-generator": "9.5.1",
|
|
58
|
-
"prettier": "3.9.
|
|
58
|
+
"prettier": "3.9.9",
|
|
59
59
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
60
60
|
"typescript": "5.9.3"
|
|
61
61
|
}
|