usql 1.0.3 → 1.0.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.
@@ -1,5 +1,9 @@
1
1
  name: Publish to NPM
2
2
 
3
+ permissions:
4
+ id-token: write # Required for OIDC
5
+ contents: read
6
+
3
7
  on:
4
8
  release:
5
9
  types: [published]
@@ -8,26 +12,29 @@ jobs:
8
12
  build:
9
13
  runs-on: ubuntu-latest
10
14
  steps:
11
- - uses: actions/checkout@v1
12
- - uses: actions/setup-node@v1
15
+ - uses: actions/checkout@v6
16
+ - uses: pnpm/action-setup@v5
17
+ - uses: actions/setup-node@v6
13
18
  with:
14
- node-version: 12
15
- - run: yarn install
16
- - run: yarn build
17
- - run: yarn test
19
+ node-version: "24"
20
+ cache: "pnpm"
21
+ - run: pnpm install
22
+ - run: NODE_OPTIONS=--openssl-legacy-provider pnpm run build --if-present
23
+ - run: pnpm test
18
24
 
19
25
  publish-npm:
20
26
  needs: build
21
27
  runs-on: ubuntu-latest
22
28
  steps:
23
- - uses: actions/checkout@v1
24
- - uses: actions/setup-node@v1
29
+ - uses: actions/checkout@v6
30
+ - uses: pnpm/action-setup@v5
31
+ - uses: actions/setup-node@v6
25
32
  with:
26
- node-version: 12
27
- registry-url: https://registry.npmjs.org/
28
- - run: yarn install
29
- - run: yarn build
30
- - run: yarn test
31
- - run: yarn publish --new-version ${GITHUB_REF#"refs/tags/"} --no-git-tag-version
32
- env:
33
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
33
+ node-version: "24"
34
+ cache: "pnpm"
35
+ registry-url: "https://registry.npmjs.org/"
36
+ - run: npm -v
37
+ - run: pnpm install --frozen-lockfile
38
+ - run: NODE_OPTIONS=--openssl-legacy-provider pnpm run build --if-present
39
+ - run: pnpm test
40
+ - run: pnpm publish --no-git-checks
@@ -6,11 +6,12 @@ jobs:
6
6
  build:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
- - uses: actions/checkout@v1
10
- - uses: actions/setup-node@v1
9
+ - uses: actions/checkout@v6
10
+ - uses: pnpm/action-setup@v5
11
+ - uses: actions/setup-node@v6
11
12
  with:
12
- node-version: 12
13
- - run: yarn install
14
- - run: yarn build
15
- - run: yarn test
16
-
13
+ node-version: "24"
14
+ cache: "pnpm"
15
+ - run: pnpm install --frozen-lockfile
16
+ - run: NODE_OPTIONS=--openssl-legacy-provider pnpm run build --if-present
17
+ - run: pnpm test
package/.tool-versions CHANGED
@@ -1 +1 @@
1
- nodejs 16.20.2
1
+ nodejs 22.10.0
package/README.md CHANGED
@@ -1,259 +1,467 @@
1
1
  # µSQL
2
2
 
3
3
  An easy-to-use super tiny flexible and 0-dependency SQL query builder with Knex compatible API!
4
- Work both in browser and as node package.
4
+ Works both in the browser and as a Node.js package.
5
5
 
6
6
  ## Installation
7
+
7
8
  ### yarn
9
+
8
10
  ```sh
9
11
  yarn add usql
10
12
  ```
13
+
11
14
  ### npm
15
+
12
16
  ```sh
13
17
  npm install usql
14
18
  ```
15
19
 
16
- ## Usage
20
+ ## Quick Start
17
21
 
18
22
  ```js
19
- import USql from 'usql'
23
+ import USql from "usql";
20
24
 
21
- const sql = new USql('table').where({ 'column': '5', 'column2': '4' })
25
+ const sql = new USql("table").where({ column: "5", column2: "4" });
22
26
  ```
27
+
23
28
  Then `sql.toString()` will produce:
29
+
24
30
  ```sql
25
31
  SELECT * FROM `table` WHERE `column` = "5" AND `column2` = "4"
26
32
  ```
27
33
 
34
+ ---
35
+
36
+ ## Security
37
+
38
+ ### Parameterised queries (recommended)
39
+
40
+ The safest way to execute a query is via `.toSQL()`, which returns a `{ sql, bindings }`
41
+ object. Pass `sql` and `bindings` directly to your database driver so it handles
42
+ value escaping:
43
+
44
+ ```js
45
+ const { sql, bindings } = new USql("users").where("id", userId).toSQL();
46
+ // sql → 'SELECT * FROM `users` WHERE `id` = ?'
47
+ // bindings → [userId]
48
+
49
+ await db.execute(sql, bindings); // driver binds values safely
50
+ ```
51
+
52
+ `.toString()` is still available for logging and debugging, but it relies on inline
53
+ string escaping. Prefer `.toSQL()` for any code that talks to a real database.
54
+
55
+ ### `DB.raw()` — use with care
56
+
57
+ `DB.raw()` inserts its argument **verbatim** into the generated SQL with **no escaping
58
+ or validation**. Only pass hard-coded string literals or values you have already
59
+ fully validated yourself:
60
+
61
+ ```js
62
+ // ✅ Safe — hard-coded fragment
63
+ DB.raw("COUNT(*) as total");
64
+
65
+ // ❌ UNSAFE — never pass user input
66
+ DB.raw(req.query.column);
67
+ ```
68
+
69
+ ### Input validation
70
+
71
+ The following methods throw on invalid input rather than silently producing injectable SQL:
72
+
73
+ | Method | Throws | When |
74
+ | --------------------- | ------------ | ---------------------------------------- |
75
+ | `where(col, op, val)` | `RangeError` | `op` is not in the allowed operator set |
76
+ | `join(…, op, …)` | `RangeError` | `op` is not in the allowed operator set |
77
+ | `orderBy(col, dir)` | `RangeError` | `dir` is not `'ASC'` or `'DESC'` |
78
+ | `limit(n)` | `TypeError` | `n` cannot be parsed as a finite integer |
79
+ | `offset(n)` | `TypeError` | `n` cannot be parsed as a finite integer |
80
+ | `where(null, …)` | `TypeError` | column argument is `null` or `undefined` |
81
+
82
+ Allowed comparison operators: `=`, `!=`, `<>`, `<`, `>`, `<=`, `>=`, `LIKE`, `NOT LIKE`, `IN`, `NOT IN`, `IS`, `IS NOT`.
83
+
84
+ ---
85
+
28
86
  ## API
87
+
29
88
  ### Column selection
30
- #### select — .select([*columns])
89
+
90
+ #### select — `.select([...columns])`
91
+
31
92
  ```js
32
- new USql('books').select('title', 'author', 'year')
93
+ new USql("books").select("title", "author", "year");
33
94
  ```
95
+
34
96
  Result:
97
+
35
98
  ```sql
36
99
  SELECT `title`, `author`, `year` FROM `books`
37
100
  ```
38
- Actually select is totally optional. When it isn't set then `*` will be used:
101
+
102
+ `select` is optional — when omitted, `*` is used:
103
+
39
104
  ```js
40
- new USql('books')
105
+ new USql("books");
41
106
  ```
107
+
42
108
  Result:
109
+
43
110
  ```sql
44
111
  SELECT * FROM `books`
45
112
  ```
46
113
 
114
+ ---
115
+
47
116
  ### Where Methods
48
- #### where — .where(~mixed~)
49
117
 
50
- Object Syntax:
118
+ #### where — `.where(~mixed~)`
119
+
120
+ Object syntax:
121
+
51
122
  ```js
52
- new USql('table').where({
53
- first_name: 'Test',
54
- last_name: 'User'
55
- }).select('id')
123
+ new USql("users")
124
+ .where({
125
+ first_name: "Test",
126
+ last_name: "User",
127
+ })
128
+ .select("id");
56
129
  ```
130
+
57
131
  Result:
132
+
58
133
  ```sql
59
- SELECT `id` FROM `users` WHERE `first_name` = 'Test' AND `last_name` = 'User'
134
+ SELECT `id` FROM `users` WHERE `first_name` = "Test" AND `last_name` = "User"
60
135
  ```
61
136
 
62
- Key, Value:
137
+ Key/value (defaults to `=`):
138
+
63
139
  ```js
64
- new USql('table').where('id', 1).where('info', null)
140
+ new USql("users").where("id", 1).where("info", null);
65
141
  ```
142
+
66
143
  Result:
144
+
67
145
  ```sql
68
146
  SELECT * FROM `users` WHERE `id` = "1" AND `info` IS NULL
69
147
  ```
70
148
 
71
- Could be chained with other methods and with itself:
149
+ Three-argument form (explicit operator):
150
+
151
+ ```js
152
+ new USql("users").where("age", ">=", 18);
153
+ ```
154
+
155
+ Result:
156
+
157
+ ```sql
158
+ SELECT * FROM `users` WHERE `age` >= "18"
159
+ ```
160
+
161
+ Can be chained:
162
+
72
163
  ```js
73
- new USql('table').where('id', 1).whereNot('role', 'admin').orWhere({ 'created_at': Date.now() }).where({ 'is_deleted': 0 })
164
+ new USql("table")
165
+ .where("id", 1)
166
+ .whereNot("role", "admin")
167
+ .orWhere({ created_at: Date.now() })
168
+ .where({ is_deleted: 0 });
74
169
  ```
170
+
75
171
  Result:
172
+
76
173
  ```sql
77
174
  SELECT * FROM `table` WHERE `id` = "1" AND `role` != "admin" OR `created_at` = "1576417577608" AND `is_deleted` = "0"
78
175
  ```
79
176
 
80
- ****
177
+ ---
81
178
 
82
- #### whereNot — .whereNot(~mixed~)
179
+ #### whereNot — `.whereNot(~mixed~)`
180
+
181
+ Object syntax:
83
182
 
84
- Object Syntax:
85
183
  ```js
86
- new USql('table').whereNot({
87
- first_name: 'Test',
88
- last_name: 'User'
89
- }).select('id')
184
+ new USql("users")
185
+ .whereNot({
186
+ first_name: "Test",
187
+ last_name: "User",
188
+ })
189
+ .select("id");
90
190
  ```
191
+
91
192
  Result:
193
+
92
194
  ```sql
93
- SELECT `id` FROM `users` WHERE `first_name` != 'Test' AND `last_name` != 'User'
195
+ SELECT `id` FROM `users` WHERE `first_name` != "Test" AND `last_name` != "User"
94
196
  ```
95
197
 
96
- Key, Value:
198
+ Key/value:
199
+
97
200
  ```js
98
- new USql('table').whereNot('id', 1).whereNot('name', null)
201
+ new USql("users").whereNot("id", 1).whereNot("name", null);
99
202
  ```
203
+
100
204
  Result:
205
+
101
206
  ```sql
102
207
  SELECT * FROM `users` WHERE `id` != "1" AND `name` IS NOT NULL
103
208
  ```
104
209
 
105
- Could be chained with other methods and with itself.
210
+ ---
211
+
212
+ #### orWhere — `.orWhere(~mixed~)`
213
+
214
+ Object syntax:
215
+
216
+ ```js
217
+ new USql("users")
218
+ .orWhere({
219
+ first_name: "Test",
220
+ last_name: "User",
221
+ })
222
+ .select("id");
223
+ ```
224
+
225
+ Result:
106
226
 
107
- ****
227
+ ```sql
228
+ SELECT `id` FROM `users` WHERE `first_name` = "Test" OR `last_name` = "User"
229
+ ```
108
230
 
109
- #### orWhere — .orWhere(~mixed~)
231
+ Key/value:
110
232
 
111
- Object Syntax:
112
233
  ```js
113
- new USql('table').orWhere({
114
- first_name: 'Test',
115
- last_name: 'User'
116
- }).select('id')
234
+ new USql("users").orWhere("id", 1).orWhere("name", null);
117
235
  ```
236
+
118
237
  Result:
238
+
119
239
  ```sql
120
- SELECT `id` FROM `users` WHERE `first_name` != 'Test' OR `last_name` != 'User'
240
+ SELECT * FROM `users` WHERE `id` = "1" OR `name` IS NULL
121
241
  ```
122
242
 
123
- Key, Value:
243
+ ---
244
+
245
+ #### whereGroup — `.whereGroup(callback)`
246
+
247
+ #### orWhereGroup — `.orWhereGroup(callback)`
248
+
249
+ Group conditions in parentheses to control AND/OR precedence. The callback receives
250
+ a fresh builder; call `.where()` / `.orWhere()` on it to populate the group.
251
+
124
252
  ```js
125
- new USql('table').orWhere('id', 1).orWhere('name', null)
253
+ new USql("users")
254
+ .where("active", 1)
255
+ .whereGroup((q) => q.where("role", "admin").orWhere("role", "moderator"));
126
256
  ```
257
+
127
258
  Result:
259
+
128
260
  ```sql
129
- SELECT * FROM `users` WHERE `id` != "1" OR `name` IS NOT NULL
261
+ SELECT * FROM `users` WHERE `active` = "1" AND (`role` = "admin" OR `role` = "moderator")
130
262
  ```
131
263
 
132
- Could be chained with other methods and with itself.
264
+ ```js
265
+ new USql("users")
266
+ .where("is_deleted", 0)
267
+ .orWhereGroup((q) => q.where("role", "superadmin").where("active", 1));
268
+ ```
133
269
 
134
- ****
270
+ Result:
271
+
272
+ ```sql
273
+ SELECT * FROM `users` WHERE `is_deleted` = "0" OR (`role` = "superadmin" AND `active` = "1")
274
+ ```
275
+
276
+ > **Why does this matter?** Without grouping, SQL evaluates `AND` before `OR`, so
277
+ > `.where('a', 1).orWhere('b', 2).where('c', 3)` produces
278
+ > `a = 1 OR b = 2 AND c = 3`, which is `a = 1 OR (b = 2 AND c = 3)` — almost never
279
+ > what you want in an authorization check. Use `whereGroup` / `orWhereGroup` to make
280
+ > precedence explicit.
281
+
282
+ ---
135
283
 
136
284
  ### Join method
137
- #### join — .join(table, first, [operator], second)
138
285
 
139
- Syntax:
286
+ #### join — `.join(table, first, [operator], second)`
287
+
140
288
  ```js
141
- new USql('table')
142
- .join('contacts', 'users.id', '=', 'contacts.user_id')
143
- .select('id')
289
+ new USql("table")
290
+ .join("contacts", "users.id", "=", "contacts.user_id")
291
+ .select("id");
144
292
  ```
293
+
145
294
  Result:
295
+
146
296
  ```sql
147
297
  SELECT `id` FROM `table` JOIN `contacts` ON `users`.`id` = `contacts`.`user_id`
148
298
  ```
149
299
 
150
- You can omit the operator value:
300
+ You can omit the operator (defaults to `=`):
301
+
151
302
  ```js
152
- new USql('table')
153
- .join('contacts', 'users.id', 'contacts.user_id')
154
- .select('id')
303
+ new USql("table").join("contacts", "users.id", "contacts.user_id").select("id");
155
304
  ```
305
+
156
306
  Result:
307
+
157
308
  ```sql
158
309
  SELECT `id` FROM `table` JOIN `contacts` ON `users`.`id` = `contacts`.`user_id`
159
310
  ```
160
311
 
161
- Could be chained with other methods and with itself.
312
+ ---
162
313
 
163
- ****
314
+ ### Ordering
164
315
 
165
- ### ClearClauses
166
- #### orderBy — .orderBy(column|columns, [direction])
316
+ #### orderBy — `.orderBy(column, [direction])`
167
317
 
168
- Adds an order by clause to the query. column can be string, or list mixed with string and object.
318
+ Direction defaults to `ASC` and is normalised to uppercase.
169
319
 
170
320
  ```js
171
- new USql('table')
172
- .orderBy('table1.column1_value', 'desc')
321
+ new USql("table").orderBy("table1.column1_value", "DESC");
173
322
  ```
323
+
174
324
  Result:
325
+
175
326
  ```sql
176
- SELECT * FROM `table1` ORDER BY `table1`.`column1_value` desc
327
+ SELECT * FROM `table1` ORDER BY `table1`.`column1_value` DESC
177
328
  ```
178
329
 
179
- Multiple orderBy syntax:
330
+ Multiple columns:
331
+
180
332
  ```js
181
- new USql('table')
182
- .orderBy('table1.column1_value', 'desc')
183
- .orderBy('table1.column2_value', 'asc')
333
+ new USql("table")
334
+ .orderBy("table1.column1_value", "DESC")
335
+ .orderBy("table1.column2_value", "ASC");
184
336
  ```
337
+
185
338
  Result:
339
+
186
340
  ```sql
187
- SELECT * FROM `table1` ORDER BY `table1`.`column1_value` desc, `table1`.`column2_value` asc
341
+ SELECT * FROM `table1` ORDER BY `table1`.`column1_value` DESC, `table1`.`column2_value` ASC
188
342
  ```
189
343
 
190
- ***
344
+ ---
191
345
 
192
- #### limit — .limit(value)
346
+ ### Pagination
193
347
 
194
- Adds a limit clause to the query.
348
+ #### limit `.limit(value)`
195
349
 
196
350
  ```js
197
- new USql('table').limit(2)
351
+ new USql("table").limit(2);
198
352
  ```
353
+
199
354
  Result:
355
+
200
356
  ```sql
201
- SELECT * FROM `table1` LIMIT 2
357
+ SELECT * FROM `table` LIMIT 2
202
358
  ```
203
359
 
204
- ***
360
+ #### offset — `.offset(value)`
205
361
 
206
- #### offset .offset(value)
362
+ Requires `limit()` to be set; ignored otherwise.
207
363
 
208
- Adds an offset clause to the query. Doesn't work without explicit set of limit value
209
364
  ```js
210
- new USql('table').limit(2).offset(5)
365
+ new USql("table").limit(2).offset(5);
211
366
  ```
367
+
212
368
  Result:
369
+
213
370
  ```sql
214
- SELECT * FROM `table1` LIMIT 5, 2
371
+ SELECT * FROM `table` LIMIT 5, 2
215
372
  ```
216
373
 
217
- #### as — .as(name)
374
+ ---
375
+
376
+ ### Aliasing
377
+
378
+ #### as — `.as(name)`
379
+
380
+ Alias a sub-query. Ignored when the query is used at the top level.
218
381
 
219
- Allows for aliasing a subquery, taking the string you wish to name the current query. If the query is not a sub-query, it will be ignored.
220
382
  ```js
221
- new USql('table').select('column').as('subquery')
383
+ new USql("table").select("column").as("subquery");
222
384
  ```
385
+
223
386
  Result:
387
+
224
388
  ```sql
225
389
  (SELECT `column` FROM `table`) as `subquery`
226
390
  ```
227
391
 
228
- Usage:
392
+ Full sub-query example:
229
393
 
230
394
  ```js
231
- const subquery = new USql('groups').select('groups.name').where('users.group_id', USql.raw('`groups`.`id`')).as('group_name')
232
-
233
- const sql = new USql('users').select('users.*', subquery)
395
+ const subquery = new USql("groups")
396
+ .select("groups.name")
397
+ .where("users.group_id", USql.raw("`groups`.`id`"))
398
+ .as("group_name");
234
399
 
400
+ const sql = new USql("users").select("users.*", subquery);
235
401
  ```
402
+
236
403
  Result:
404
+
237
405
  ```sql
238
406
  SELECT `users`.*, (SELECT `groups`.`name` FROM `groups` WHERE `users`.`group_id` = `groups`.`id`) as `group_name` FROM `users`
239
407
  ```
240
408
 
241
- ***
409
+ ---
242
410
 
243
- ### Raw queries
411
+ ### Cloning
412
+
413
+ #### clone — `.clone()`
414
+
415
+ Returns a deep copy of the builder. Use this when you want to reuse a base query
416
+ across multiple code paths without risk of shared-state mutation:
417
+
418
+ ```js
419
+ const base = new USql("users").where("active", 1);
244
420
 
245
- ### raw raw(statement)
421
+ const admins = base.clone().where("role", "admin").limit(10);
422
+ const mods = base.clone().where("role", "moderator");
246
423
 
247
- Run an arbitrary sql query in the schema builder chain.
424
+ // base is unchanged
425
+ ```
426
+
427
+ ---
428
+
429
+ ### Parameterised output
430
+
431
+ #### toSQL — `.toSQL()`
432
+
433
+ Returns `{ sql, bindings }` with `?` placeholders instead of inline values.
434
+ Pass both to your database driver for safe parameterised execution.
435
+
436
+ ```js
437
+ const { sql, bindings } = new USql("users")
438
+ .where("email", userEmail)
439
+ .where("active", 1)
440
+ .toSQL();
441
+
442
+ // sql → 'SELECT * FROM `users` WHERE `email` = ? AND `active` = ?'
443
+ // bindings → [userEmail, 1]
248
444
 
249
- Syntax:
445
+ await connection.execute(sql, bindings);
446
+ ```
447
+
448
+ ---
449
+
450
+ ### Raw queries
451
+
452
+ #### raw — `USql.raw(statement)`
453
+
454
+ Inserts a raw SQL fragment verbatim. See the [Security](#security) section for
455
+ important warnings before use.
250
456
 
251
457
  ```js
252
- new USql('users').select(DB.raw('count(*) as item_number'))
458
+ new USql("users").select(USql.raw("count(*) as item_number"));
253
459
  ```
460
+
254
461
  Result:
462
+
255
463
  ```sql
256
- SELECT count(*) as item_number FROM `table`
464
+ SELECT count(*) as item_number FROM `users`
257
465
  ```
258
466
 
259
- Raw supported mostly everywhere including: select, where statments, join (for example for table aliasing) and order by column name.
467
+ `raw` is supported in `select`, `where`, `join`, and `orderBy`.