sql-love 1.0.6 → 1.1.1
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/LICENSE.md +1 -1
- package/README.md +67 -29
- package/dist/index.cjs +12 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.mjs +12 -1
- package/package.json +9 -8
package/LICENSE.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
ISC License (ISC)
|
|
2
|
-
Copyright 2023-
|
|
2
|
+
Copyright 2023-2026 Ken Snyder
|
|
3
3
|
|
|
4
4
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
5
5
|
|
package/README.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
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://www.npmjs.com/package/sql-love)
|
|
12
|
-
[](https://opensource.org/licenses/ISC)
|
|
5
|
+
[](https://npmjs.com/package/sql-love)
|
|
6
|
+
[](https://github.com/search?q=repo:kensnyder/sql-love++language:TypeScript&type=code)
|
|
7
|
+
[](https://codecov.io/gh/kensnyder/sql-love)
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
[](https://opensource.org/licenses/ISC)
|
|
13
11
|
|
|
14
12
|
Classes for parsing and building SQL select queries in Node/Bun/Deno.
|
|
15
13
|
|
|
@@ -68,7 +66,7 @@ if (areaCode) {
|
|
|
68
66
|
}
|
|
69
67
|
query.sort(sortField);
|
|
70
68
|
query.limit(limitTo);
|
|
71
|
-
const { sql, bindings } = query.compile();
|
|
69
|
+
const { sql, bindings } = query.compile({ engine: 'pg' });
|
|
72
70
|
|
|
73
71
|
//
|
|
74
72
|
// Then execute the SQL in your preferred client:
|
|
@@ -190,15 +188,16 @@ Use the following methods to build queries. And see
|
|
|
190
188
|
- `query.having(expression)` - Having an arbitrary expression
|
|
191
189
|
- `query.having(columnValueRecord)` - Add multiple conditions
|
|
192
190
|
- `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
|
|
191
|
+
- `query.orderBy(column, direction)` - Add ORDER BY clause
|
|
192
|
+
- `query.sortField(column, mapNames, modifier)` - Add ORDER BY clause with mapNames
|
|
195
193
|
- `query.limit(num)` - Limit by the given number
|
|
196
194
|
- `query.offset(num)` - Specify an offset
|
|
197
195
|
- `query.page(num)` - Automatically calculate offset based on limit and page
|
|
198
196
|
- `query.columns(columnNames)` - Add column names to fetch (alias `select()`)
|
|
199
197
|
- `query.column(columnName)` - Add a column name to fetch
|
|
200
|
-
- `query.table(tableName)` -
|
|
201
|
-
- `query.
|
|
198
|
+
- `query.table(tableName)` - Add a table to add to the FROM clause
|
|
199
|
+
- `query.tables(tableNames)` - Add an array of tables to add to the FROM clause
|
|
200
|
+
- `query.from(tableName)` - Alias for `table()`
|
|
202
201
|
- `query.join(expression)` - Add a JOIN expression
|
|
203
202
|
- `query.innerJoin(expression)` - Add an INNER JOIN expression
|
|
204
203
|
- `query.leftJoin(expression)` - Add a LEFT JOIN expression
|
|
@@ -213,7 +212,13 @@ Use the following methods to build queries. And see
|
|
|
213
212
|
### More examples
|
|
214
213
|
|
|
215
214
|
```js
|
|
216
|
-
//
|
|
215
|
+
// building the FROM clause
|
|
216
|
+
query.column('id');
|
|
217
|
+
query.column('"Media"."postId"');
|
|
218
|
+
query.column('CONCAT(first_name, " ", last_name) AS full_name');
|
|
219
|
+
query.columns(['id', 'first_name', 'last_name']);
|
|
220
|
+
|
|
221
|
+
// Ways to test against NULL
|
|
217
222
|
query.where('deleted_at', '=', null);
|
|
218
223
|
query.where('deleted_at', '=', undefined);
|
|
219
224
|
query.where('deleted_at', null);
|
|
@@ -222,15 +227,16 @@ query.where('deleted_at IS NULL');
|
|
|
222
227
|
query.where({ deleted_at: null });
|
|
223
228
|
query.where({ deleted_at: undefined });
|
|
224
229
|
|
|
225
|
-
//
|
|
230
|
+
// Using LIKE
|
|
226
231
|
query.where('name', 'LIKE', 'son');
|
|
227
232
|
query.where('name', 'LIKE ?', 'son');
|
|
228
233
|
query.where('name', 'LIKE ?%', 'son');
|
|
229
234
|
query.where('name', 'LIKE %?', 'son');
|
|
230
235
|
query.where('name', 'LIKE %?%', 'son');
|
|
231
236
|
query.where('name', 'LIKE', '%son%');
|
|
237
|
+
query.where('name', 'ILIKE %?', 'son');
|
|
232
238
|
|
|
233
|
-
//
|
|
239
|
+
// Various operators
|
|
234
240
|
query.where('price', '=', 100);
|
|
235
241
|
query.where('price', '!=', 100);
|
|
236
242
|
query.where('price', '<>', 100);
|
|
@@ -243,7 +249,7 @@ query.whereBetween('price', [100, null]); // price > 100
|
|
|
243
249
|
query.whereBetween('price', [null, 200]); // price < 200
|
|
244
250
|
query.whereBetween('price', [null, null]); // clause is ignored
|
|
245
251
|
|
|
246
|
-
//
|
|
252
|
+
// Using IN and NOT IN
|
|
247
253
|
query.where('status', 'IN', ['pending', 'approved']);
|
|
248
254
|
query.where('status', ['pending', 'approved']);
|
|
249
255
|
query.where('status', '=', ['pending', 'approved']);
|
|
@@ -254,7 +260,7 @@ query.where({ 'status NOT IN': ['pending', 'approved'] });
|
|
|
254
260
|
query.where({ 'status !=': ['pending', 'approved'] });
|
|
255
261
|
query.where({ 'status <>': ['pending', 'approved'] });
|
|
256
262
|
|
|
257
|
-
//
|
|
263
|
+
// Question marks for binding
|
|
258
264
|
query.where(
|
|
259
265
|
'users.id IN (SELECT user_id FROM roles WHERE customer_id IN (?, ?))',
|
|
260
266
|
[1, 2]
|
|
@@ -265,7 +271,7 @@ query.where(
|
|
|
265
271
|
[1]
|
|
266
272
|
);
|
|
267
273
|
|
|
268
|
-
//
|
|
274
|
+
// Multiple conditions
|
|
269
275
|
query.where({
|
|
270
276
|
deleted_at: null,
|
|
271
277
|
'price >': 100,
|
|
@@ -277,7 +283,7 @@ query.where({
|
|
|
277
283
|
|
|
278
284
|
// Note: .having() supports the same signatures as .where()
|
|
279
285
|
|
|
280
|
-
//
|
|
286
|
+
// Ways to use OR
|
|
281
287
|
query.orWhere([{ approved_at: null }, { denied_at: null }]);
|
|
282
288
|
query.orWhere({ approved_at: null, denied_at: null });
|
|
283
289
|
query.orWhere(['approved_at IS NULL', 'denied_at IS NULL']);
|
|
@@ -289,7 +295,7 @@ query.where('(approved_at IS NULL OR denied_at IS NULL)');
|
|
|
289
295
|
|
|
290
296
|
// Note: .orHaving() supports the same signatures as .orWhere()
|
|
291
297
|
|
|
292
|
-
//
|
|
298
|
+
// Joins
|
|
293
299
|
query.innerJoin('phone_numbers p ON p.user_id = u.id');
|
|
294
300
|
query.leftJoin('phone_numbers p ON p.user_id = u.id AND p.type = ?', ['main']);
|
|
295
301
|
query.outerJoin('phone_numbers p ON p.user_id = u.id AND p.type NOT IN(?, ?)', [
|
|
@@ -297,10 +303,40 @@ query.outerJoin('phone_numbers p ON p.user_id = u.id AND p.type NOT IN(?, ?)', [
|
|
|
297
303
|
'cell',
|
|
298
304
|
]);
|
|
299
305
|
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
//
|
|
306
|
+
// Grouping
|
|
307
|
+
|
|
308
|
+
// Sorting
|
|
309
|
+
query.orderBy('modified_at'); // ORDER BY modified_at
|
|
310
|
+
query.orderBy('modified_at', 'DESC'); // ORDER BY modified_at DESC
|
|
311
|
+
query.orderBy('-modified_at'); // ORDER BY modified_at DESC
|
|
312
|
+
query.orderBy('COUNT(*)'); // ORDER BY COUNT(*)
|
|
313
|
+
query.orderBy('COUNT(*)', 'DESC'); // ORDER BY COUNT(*) DESC
|
|
314
|
+
query.orderBy('COUNT(*) DESC'); // ORDER BY COUNT(*) DESC
|
|
315
|
+
query.orderBy('modified_at DESC NULLS LAST'); // ORDER BY modified_at DESC NULLS LAST
|
|
316
|
+
query.orderBy('modified_at USING >'); // ORDER BY modified_at USING >
|
|
317
|
+
|
|
318
|
+
// Sorting with support for hyphens and mapping
|
|
319
|
+
query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
320
|
+
query.sortField('-created', { created: 'created_at' }); // ORDER BY created_at DESC
|
|
321
|
+
query.sortField(
|
|
322
|
+
'-created_at',
|
|
323
|
+
{
|
|
324
|
+
created_at: 'posts.created_timestamp',
|
|
325
|
+
},
|
|
326
|
+
'NULLS LAST'
|
|
327
|
+
); // ORDER BY posts.created_timestamp DESC NULLS LAST
|
|
328
|
+
query.sortField('-created', {
|
|
329
|
+
created: 'created_at NULLS LAST',
|
|
330
|
+
'-created': 'created_at DESC NULLS LAST',
|
|
331
|
+
}); // ORDER BY created_at DESC NULLS LAST
|
|
332
|
+
query.sortField('-name', {
|
|
333
|
+
name: 'name COLLATE "jp_JP"',
|
|
334
|
+
}); // ORDER BY name COLLATE "jp_JP" DESC
|
|
335
|
+
|
|
336
|
+
// Pagination
|
|
337
|
+
query.limit(10).page(3); // "LIMIT 10 OFFSET 20" (or for mssql, "OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY")
|
|
338
|
+
|
|
339
|
+
// Cloning for creating similar queries
|
|
304
340
|
const query1 = new SelectBuilder();
|
|
305
341
|
query1.where('email', 'john@example.com');
|
|
306
342
|
const query2 = query1.getClone();
|
|
@@ -398,10 +434,10 @@ If you need to use SQL keywords in strings, use bindings.
|
|
|
398
434
|
|
|
399
435
|
```js
|
|
400
436
|
// ❌ WILL NOT WORK
|
|
401
|
-
new SelectBuilder(`SELECT id, CONCAT('where ',
|
|
437
|
+
new SelectBuilder(`SELECT id, CONCAT('where ', column) FROM users`);
|
|
402
438
|
|
|
403
439
|
// ✅ WORKING EQUIVALENT
|
|
404
|
-
new SelectBuilder(`SELECT id, CONCAT(:prefix,
|
|
440
|
+
new SelectBuilder(`SELECT id, CONCAT(:prefix, column) FROM users`, {
|
|
405
441
|
prefix: 'where ',
|
|
406
442
|
});
|
|
407
443
|
```
|
|
@@ -498,7 +534,9 @@ const grouped = extractGrouped('dept', records);
|
|
|
498
534
|
{ id: 1, name: 'John', dept: 'Marketing' },
|
|
499
535
|
{ id: 3, name: 'Tim', dept: 'Marketing' },
|
|
500
536
|
],
|
|
501
|
-
Finance: [
|
|
537
|
+
Finance: [
|
|
538
|
+
{ id: 2, name: 'Jane', dept: 'Finance' }
|
|
539
|
+
],
|
|
502
540
|
}
|
|
503
541
|
*/
|
|
504
542
|
```
|
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.
|
|
3
|
+
"version": "1.1.1",
|
|
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.
|
|
57
|
-
"typescript": "
|
|
54
|
+
"@types/bun": "latest",
|
|
55
|
+
"@types/node": "latest",
|
|
56
|
+
"prettier": "3.9.6",
|
|
57
|
+
"prettier-plugin-organize-imports": "4.3.0",
|
|
58
|
+
"typescript": "7.0.2"
|
|
58
59
|
}
|
|
59
60
|
}
|