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

|
|
10
|
+

|
|
11
|
+
[](https://www.npmjs.com/package/sql-love)
|
|
12
|
+
[](https://opensource.org/licenses/ISC)
|
|
13
13
|
|
|
14
14
|
Classes for parsing and building SQL select queries in Node/Bun/Deno.
|
|
15
15
|
|
|
@@ -190,15 +190,16 @@ Use the following methods to build queries. And see
|
|
|
190
190
|
- `query.having(expression)` - Having an arbitrary expression
|
|
191
191
|
- `query.having(columnValueRecord)` - Add multiple conditions
|
|
192
192
|
- `query.orHaving(expressions)` - Multiple `having()`s joined by OR
|
|
193
|
-
- `query.orderBy(column)` - Add ORDER BY clause
|
|
194
|
-
- `query.sortField(column, mapNames)` - Add ORDER BY clause with mapNames
|
|
193
|
+
- `query.orderBy(column, direction)` - Add ORDER BY clause
|
|
194
|
+
- `query.sortField(column, mapNames, modifier)` - Add ORDER BY clause with mapNames
|
|
195
195
|
- `query.limit(num)` - Limit by the given number
|
|
196
196
|
- `query.offset(num)` - Specify an offset
|
|
197
197
|
- `query.page(num)` - Automatically calculate offset based on limit and page
|
|
198
198
|
- `query.columns(columnNames)` - Add column names to fetch (alias `select()`)
|
|
199
199
|
- `query.column(columnName)` - Add a column name to fetch
|
|
200
|
-
- `query.table(tableName)` -
|
|
201
|
-
- `query.
|
|
200
|
+
- `query.table(tableName)` - Add a table to add to the FROM clause
|
|
201
|
+
- `query.tables(tableNames)` - Add an array of tables to add to the FROM clause
|
|
202
|
+
- `query.from(tableName)` - Alias for `table()`
|
|
202
203
|
- `query.join(expression)` - Add a JOIN expression
|
|
203
204
|
- `query.innerJoin(expression)` - Add an INNER JOIN expression
|
|
204
205
|
- `query.leftJoin(expression)` - Add a LEFT JOIN expression
|
|
@@ -213,7 +214,13 @@ Use the following methods to build queries. And see
|
|
|
213
214
|
### More examples
|
|
214
215
|
|
|
215
216
|
```js
|
|
216
|
-
//
|
|
217
|
+
// building the FROM clause
|
|
218
|
+
query.column('id');
|
|
219
|
+
query.column('"Media"."postId"');
|
|
220
|
+
query.column('CONCAT(first_name, " ", last_name) AS full_name');
|
|
221
|
+
query.columns(['id', 'first_name', 'last_name']);
|
|
222
|
+
|
|
223
|
+
// Ways to test against NULL
|
|
217
224
|
query.where('deleted_at', '=', null);
|
|
218
225
|
query.where('deleted_at', '=', undefined);
|
|
219
226
|
query.where('deleted_at', null);
|
|
@@ -222,15 +229,16 @@ query.where('deleted_at IS NULL');
|
|
|
222
229
|
query.where({ deleted_at: null });
|
|
223
230
|
query.where({ deleted_at: undefined });
|
|
224
231
|
|
|
225
|
-
//
|
|
232
|
+
// Using LIKE
|
|
226
233
|
query.where('name', 'LIKE', 'son');
|
|
227
234
|
query.where('name', 'LIKE ?', 'son');
|
|
228
235
|
query.where('name', 'LIKE ?%', 'son');
|
|
229
236
|
query.where('name', 'LIKE %?', 'son');
|
|
230
237
|
query.where('name', 'LIKE %?%', 'son');
|
|
231
238
|
query.where('name', 'LIKE', '%son%');
|
|
239
|
+
query.where('name', 'ILIKE %?', 'son');
|
|
232
240
|
|
|
233
|
-
//
|
|
241
|
+
// Various operators
|
|
234
242
|
query.where('price', '=', 100);
|
|
235
243
|
query.where('price', '!=', 100);
|
|
236
244
|
query.where('price', '<>', 100);
|
|
@@ -243,7 +251,7 @@ query.whereBetween('price', [100, null]); // price > 100
|
|
|
243
251
|
query.whereBetween('price', [null, 200]); // price < 200
|
|
244
252
|
query.whereBetween('price', [null, null]); // clause is ignored
|
|
245
253
|
|
|
246
|
-
//
|
|
254
|
+
// Using IN and NOT IN
|
|
247
255
|
query.where('status', 'IN', ['pending', 'approved']);
|
|
248
256
|
query.where('status', ['pending', 'approved']);
|
|
249
257
|
query.where('status', '=', ['pending', 'approved']);
|
|
@@ -254,7 +262,7 @@ query.where({ 'status NOT IN': ['pending', 'approved'] });
|
|
|
254
262
|
query.where({ 'status !=': ['pending', 'approved'] });
|
|
255
263
|
query.where({ 'status <>': ['pending', 'approved'] });
|
|
256
264
|
|
|
257
|
-
//
|
|
265
|
+
// Question marks for binding
|
|
258
266
|
query.where(
|
|
259
267
|
'users.id IN (SELECT user_id FROM roles WHERE customer_id IN (?, ?))',
|
|
260
268
|
[1, 2]
|
|
@@ -265,7 +273,7 @@ query.where(
|
|
|
265
273
|
[1]
|
|
266
274
|
);
|
|
267
275
|
|
|
268
|
-
//
|
|
276
|
+
// Multiple conditions
|
|
269
277
|
query.where({
|
|
270
278
|
deleted_at: null,
|
|
271
279
|
'price >': 100,
|
|
@@ -277,7 +285,7 @@ query.where({
|
|
|
277
285
|
|
|
278
286
|
// Note: .having() supports the same signatures as .where()
|
|
279
287
|
|
|
280
|
-
//
|
|
288
|
+
// Ways to use OR
|
|
281
289
|
query.orWhere([{ approved_at: null }, { denied_at: null }]);
|
|
282
290
|
query.orWhere({ approved_at: null, denied_at: null });
|
|
283
291
|
query.orWhere(['approved_at IS NULL', 'denied_at IS NULL']);
|
|
@@ -289,7 +297,7 @@ query.where('(approved_at IS NULL OR denied_at IS NULL)');
|
|
|
289
297
|
|
|
290
298
|
// Note: .orHaving() supports the same signatures as .orWhere()
|
|
291
299
|
|
|
292
|
-
//
|
|
300
|
+
// Joins
|
|
293
301
|
query.innerJoin('phone_numbers p ON p.user_id = u.id');
|
|
294
302
|
query.leftJoin('phone_numbers p ON p.user_id = u.id AND p.type = ?', ['main']);
|
|
295
303
|
query.outerJoin('phone_numbers p ON p.user_id = u.id AND p.type NOT IN(?, ?)', [
|
|
@@ -297,10 +305,40 @@ query.outerJoin('phone_numbers p ON p.user_id = u.id AND p.type NOT IN(?, ?)', [
|
|
|
297
305
|
'cell',
|
|
298
306
|
]);
|
|
299
307
|
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
//
|
|
308
|
+
// Grouping
|
|
309
|
+
|
|
310
|
+
// Sorting
|
|
311
|
+
query.orderBy('modified_at'); // ORDER BY modified_at
|
|
312
|
+
query.orderBy('modified_at', 'DESC'); // ORDER BY modified_at DESC
|
|
313
|
+
query.orderBy('-modified_at'); // ORDER BY modified_at DESC
|
|
314
|
+
query.orderBy('COUNT(*)'); // ORDER BY COUNT(*)
|
|
315
|
+
query.orderBy('COUNT(*)', 'DESC'); // ORDER BY COUNT(*) DESC
|
|
316
|
+
query.orderBy('COUNT(*) DESC'); // ORDER BY COUNT(*) DESC
|
|
317
|
+
query.orderBy('modified_at DESC NULLS LAST'); // ORDER BY modified_at DESC NULLS LAST
|
|
318
|
+
query.orderBy('modified_at USING >'); // ORDER BY modified_at USING >
|
|
319
|
+
|
|
320
|
+
// Sorting with support for hyphens and mapping
|
|
321
|
+
query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
322
|
+
query.sortField('-created', { created: 'created_at' }); // ORDER BY created_at DESC
|
|
323
|
+
query.sortField(
|
|
324
|
+
'-created_at',
|
|
325
|
+
{
|
|
326
|
+
created_at: 'posts.created_timestamp',
|
|
327
|
+
},
|
|
328
|
+
'NULLS LAST'
|
|
329
|
+
); // ORDER BY posts.created_timestamp DESC NULLS LAST
|
|
330
|
+
query.sortField('-created', {
|
|
331
|
+
created: 'created_at NULLS LAST',
|
|
332
|
+
'-created': 'created_at DESC NULLS LAST',
|
|
333
|
+
}); // ORDER BY created_at DESC NULLS LAST
|
|
334
|
+
query.sortField('-name', {
|
|
335
|
+
name: 'name COLLATE "jp_JP"',
|
|
336
|
+
}); // ORDER BY name COLLATE "jp_JP" DESC
|
|
337
|
+
|
|
338
|
+
// Pagination
|
|
339
|
+
query.limit(10).page(3); // "LIMIT 10 OFFSET 20" (or for mssql, "OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY")
|
|
340
|
+
|
|
341
|
+
// Cloning for creating similar queries
|
|
304
342
|
const query1 = new SelectBuilder();
|
|
305
343
|
query1.where('email', 'john@example.com');
|
|
306
344
|
const query2 = query1.getClone();
|
|
@@ -398,10 +436,10 @@ If you need to use SQL keywords in strings, use bindings.
|
|
|
398
436
|
|
|
399
437
|
```js
|
|
400
438
|
// ❌ WILL NOT WORK
|
|
401
|
-
new SelectBuilder(`SELECT id, CONCAT('where ',
|
|
439
|
+
new SelectBuilder(`SELECT id, CONCAT('where ', column) FROM users`);
|
|
402
440
|
|
|
403
441
|
// ✅ WORKING EQUIVALENT
|
|
404
|
-
new SelectBuilder(`SELECT id, CONCAT(:prefix,
|
|
442
|
+
new SelectBuilder(`SELECT id, CONCAT(:prefix, column) FROM users`, {
|
|
405
443
|
prefix: 'where ',
|
|
406
444
|
});
|
|
407
445
|
```
|
|
@@ -498,7 +536,9 @@ const grouped = extractGrouped('dept', records);
|
|
|
498
536
|
{ id: 1, name: 'John', dept: 'Marketing' },
|
|
499
537
|
{ id: 3, name: 'Tim', dept: 'Marketing' },
|
|
500
538
|
],
|
|
501
|
-
Finance: [
|
|
539
|
+
Finance: [
|
|
540
|
+
{ id: 2, name: 'Jane', dept: 'Finance' }
|
|
541
|
+
],
|
|
502
542
|
}
|
|
503
543
|
*/
|
|
504
544
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -650,13 +650,24 @@ UNION
|
|
|
650
650
|
}
|
|
651
651
|
return this;
|
|
652
652
|
}
|
|
653
|
-
sortField(fieldName, mapNames = {}) {
|
|
653
|
+
sortField(fieldName, mapNames = {}, modifier = "") {
|
|
654
|
+
if (!fieldName) {
|
|
655
|
+
return this;
|
|
656
|
+
}
|
|
654
657
|
let direction = "ASC";
|
|
655
658
|
if (fieldName.startsWith("-")) {
|
|
659
|
+
if (mapNames[fieldName]) {
|
|
660
|
+
const maybeModifier = modifier ? ` ${modifier}` : "";
|
|
661
|
+
this.orderBy(mapNames[fieldName] + maybeModifier);
|
|
662
|
+
return this;
|
|
663
|
+
}
|
|
656
664
|
direction = "DESC";
|
|
657
665
|
fieldName = fieldName.slice(1);
|
|
658
666
|
}
|
|
659
667
|
const column = mapNames[fieldName] || fieldName;
|
|
668
|
+
if (modifier) {
|
|
669
|
+
direction += ` ${modifier}`;
|
|
670
|
+
}
|
|
660
671
|
this.orderBy(column, direction);
|
|
661
672
|
return this;
|
|
662
673
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -320,7 +320,7 @@ export declare class SelectBuilder {
|
|
|
320
320
|
/**
|
|
321
321
|
* Add a WHERE clause with a BETWEEN condition
|
|
322
322
|
* @param column The column name
|
|
323
|
-
* @param twoValueArray The two values to be between
|
|
323
|
+
* @param twoValueArray The two values to be between. If the first is null, it will use >= instead. If the second is null, it will use <= instead. If both are null, no criteria will be added
|
|
324
324
|
*/
|
|
325
325
|
whereBetween(column: string, twoValueArray: [
|
|
326
326
|
any,
|
|
@@ -350,21 +350,31 @@ export declare class SelectBuilder {
|
|
|
350
350
|
* @param column The column name or expression to sort by. Include DESC or prefix with - to sort descending
|
|
351
351
|
* @param [direction] The direction to sort by (ASC or DESC)
|
|
352
352
|
* @example
|
|
353
|
-
* query.
|
|
354
|
-
* query.
|
|
355
|
-
* query.
|
|
356
|
-
* query.
|
|
353
|
+
* query.orderBy('modified_at'); // ORDER BY modified_at
|
|
354
|
+
* query.orderBy('-modified_at'); // ORDER BY modified_at DESC
|
|
355
|
+
* query.orderBy('COUNT(*)'); // ORDER BY COUNT(*)
|
|
356
|
+
* query.orderBy('COUNT(*) DESC'); // ORDER BY COUNT(*) DESC
|
|
357
|
+
* query.orderBy('modified_at DESC NULLS LAST'); // ORDER BY modified_at DESC NULLS LAST
|
|
358
|
+
* query.orderBy('modified_at USING >'); // ORDER BY modified_at USING >
|
|
357
359
|
*/
|
|
358
360
|
orderBy(column: string, direction?: "ASC" | "DESC"): this;
|
|
359
361
|
/**
|
|
360
362
|
* Sort by the given column, with a map of columns to translate
|
|
361
|
-
* @param fieldName The file name such as
|
|
363
|
+
* @param fieldName The file name such as 'created_at' or '-created_at' for descending
|
|
362
364
|
* @param [mapNames] Column names to translate from field to column expression
|
|
365
|
+
* @param [modifier] Additional clause to append such as 'NULLS LAST' or 'COLLATE "en_US"'
|
|
363
366
|
* @example
|
|
364
367
|
* query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
365
368
|
* query.sortField('-created', {'created': 'created_at'}); // ORDER BY created_at DESC
|
|
366
|
-
|
|
367
|
-
|
|
369
|
+
* query.sortField('-created', {
|
|
370
|
+
* 'created': 'created_at NULLS LAST',
|
|
371
|
+
* '-created': 'created_at DESC NULLS LAST'
|
|
372
|
+
* }); // ORDER BY created_at DESC NULLS LAST
|
|
373
|
+
* query.sortField('-name', {
|
|
374
|
+
* 'name': 'name COLLATE "jp_JP"'
|
|
375
|
+
* }); // ORDER BY name COLLATE "jp_JP" DESC
|
|
376
|
+
*/
|
|
377
|
+
sortField(fieldName: string, mapNames?: Record<string, string>, modifier?: string): this;
|
|
368
378
|
/**
|
|
369
379
|
* Check to see if the given string is 0 or all digits not starting with 0
|
|
370
380
|
* @param string The input string
|
package/dist/index.mjs
CHANGED
|
@@ -600,13 +600,24 @@ UNION
|
|
|
600
600
|
}
|
|
601
601
|
return this;
|
|
602
602
|
}
|
|
603
|
-
sortField(fieldName, mapNames = {}) {
|
|
603
|
+
sortField(fieldName, mapNames = {}, modifier = "") {
|
|
604
|
+
if (!fieldName) {
|
|
605
|
+
return this;
|
|
606
|
+
}
|
|
604
607
|
let direction = "ASC";
|
|
605
608
|
if (fieldName.startsWith("-")) {
|
|
609
|
+
if (mapNames[fieldName]) {
|
|
610
|
+
const maybeModifier = modifier ? ` ${modifier}` : "";
|
|
611
|
+
this.orderBy(mapNames[fieldName] + maybeModifier);
|
|
612
|
+
return this;
|
|
613
|
+
}
|
|
606
614
|
direction = "DESC";
|
|
607
615
|
fieldName = fieldName.slice(1);
|
|
608
616
|
}
|
|
609
617
|
const column = mapNames[fieldName] || fieldName;
|
|
618
|
+
if (modifier) {
|
|
619
|
+
direction += ` ${modifier}`;
|
|
620
|
+
}
|
|
610
621
|
this.orderBy(column, direction);
|
|
611
622
|
return this;
|
|
612
623
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sql-love",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Build SQL queries with JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
15
|
"require": "./dist/index.cjs",
|
|
16
16
|
"import": "./dist/index.mjs"
|
|
17
17
|
}
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "bun test",
|
|
21
21
|
"test-watch": "bun test --watch",
|
|
22
|
-
"coverage": "bun test --coverage
|
|
22
|
+
"coverage": "bun test --coverage",
|
|
23
|
+
"coverage-lcov": "bun test --coverage --coverage-reporter=lcov",
|
|
23
24
|
"build": "bun run build:clean && bun run build:dts && bun run build:esm && bun run build:cjs",
|
|
24
25
|
"build:clean": "rm -Rf dist/ && mkdir dist && touch dist/.gitkeep",
|
|
25
26
|
"build:dts": "yes | bunx dts-bundle-generator -o dist/index.d.ts index.ts",
|
|
@@ -50,10 +51,10 @@
|
|
|
50
51
|
},
|
|
51
52
|
"homepage": "https://github.com/kensnyder/sql-love#readme",
|
|
52
53
|
"devDependencies": {
|
|
53
|
-
"@types/bun": "
|
|
54
|
-
"@types/node": "
|
|
55
|
-
"prettier": "3.
|
|
56
|
-
"prettier-plugin-organize-imports": "4.
|
|
54
|
+
"@types/bun": "latest",
|
|
55
|
+
"@types/node": "latest",
|
|
56
|
+
"prettier": "3.6.2",
|
|
57
|
+
"prettier-plugin-organize-imports": "4.2.0",
|
|
57
58
|
"typescript": "5.8.3"
|
|
58
59
|
}
|
|
59
60
|
}
|