@truto/sqlite-builder 1.0.0 → 1.0.2

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 CHANGED
@@ -2,13 +2,12 @@
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/%40truto%2Fsqlite-builder.svg)](https://badge.fury.io/js/%40truto%2Fsqlite-builder)
4
4
  [![CI](https://github.com/trutohq/truto-sqlite-builder/actions/workflows/ci.yml/badge.svg)](https://github.com/trutohq/truto-sqlite-builder/actions/workflows/ci.yml)
5
- [![codecov](https://codecov.io/gh/trutohq/truto-sqlite-builder/branch/main/graph/badge.svg)](https://codecov.io/gh/trutohq/truto-sqlite-builder)
6
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
7
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
7
 
9
8
  **Safe, zero-dependency template-literal tag for SQLite queries in any JS environment.**
10
9
 
11
- `truto-sqlite-builder` provides a secure and ergonomic way to build SQLite queries using tagged template literals. It prevents SQL injection attacks through parameterized queries while offering convenient helper functions for common SQL patterns.
10
+ `@truto/sqlite-builder` provides a secure and ergonomic way to build SQLite queries using tagged template literals. It prevents SQL injection attacks through parameterized queries while offering convenient helper functions for common SQL patterns.
12
11
 
13
12
  ## ✨ Features
14
13
 
@@ -18,31 +17,32 @@
18
17
  - 🌍 **Universal**: Works in Bun, Node.js, Deno, and modern browsers
19
18
  - 🎯 **TypeScript-first**: Full type safety with excellent IDE support
20
19
  - 🔧 **Helper functions**: Built-in utilities for identifiers, IN clauses, and more
20
+ - 🔍 **JSON Filter Language**: MongoDB-style JSON filters for WHERE clauses
21
21
  - ⚡ **Lightweight**: Minimal bundle size with tree-shaking support
22
22
 
23
23
  ## 📦 Installation
24
24
 
25
25
  ```bash
26
- bun add truto-sqlite-builder
26
+ bun add @truto/sqlite-builder
27
27
  ```
28
28
 
29
29
  ```bash
30
- npm install truto-sqlite-builder
30
+ npm install @truto/sqlite-builder
31
31
  ```
32
32
 
33
33
  ```bash
34
- yarn add truto-sqlite-builder
34
+ yarn add @truto/sqlite-builder
35
35
  ```
36
36
 
37
37
  ```bash
38
- pnpm add truto-sqlite-builder
38
+ pnpm add @truto/sqlite-builder
39
39
  ```
40
40
 
41
41
  ## 🚀 Quick Start
42
42
 
43
43
  ```typescript
44
44
  import sqlite3 from 'better-sqlite3'
45
- import { sql } from 'truto-sqlite-builder'
45
+ import { sql, compileFilter } from '@truto/sqlite-builder'
46
46
 
47
47
  const db = new sqlite3('database.db')
48
48
 
@@ -51,14 +51,17 @@ const name = 'Alice'
51
51
  const { text, values } = sql`SELECT * FROM users WHERE name = ${name}`
52
52
  const users = db.prepare(text).all(...values)
53
53
 
54
- // Complex query with helpers
55
- const userIds = [1, 2, 3]
54
+ // JSON Filter queries
55
+ const filter = {
56
+ name: { like: 'John%' },
57
+ age: { gte: 18, lt: 65 },
58
+ or: [{ email: { regex: '.*@example.com$' } }, { phone: { exists: false } }],
59
+ }
60
+
61
+ const { text: whereText, values: whereValues } = compileFilter(filter)
56
62
  const query = sql`
57
- SELECT u.*, p.title
58
- FROM ${sql.ident('users')} u
59
- JOIN ${sql.ident('posts')} p ON u.id = p.user_id
60
- WHERE u.id IN ${sql.in(userIds)}
61
- AND u.status = ${'active'}
63
+ SELECT * FROM users
64
+ WHERE ${sql.raw(whereText)}
62
65
  `
63
66
 
64
67
  const results = db.prepare(query.text).all(...query.values)
@@ -171,6 +174,251 @@ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
171
174
  // Returns: { text: "SELECT * FROM users WHERE name = ? AND age = ? AND active = ?", values: ['John', 30, true] }
172
175
  ```
173
176
 
177
+ ## 🔍 JSON Filter Language
178
+
179
+ Build complex WHERE clauses using MongoDB-style JSON filters. Perfect for APIs and dynamic queries.
180
+
181
+ ### `compileFilter(filter: JsonFilter): FilterResult`
182
+
183
+ Compiles a JSON filter object into a parameterized SQL WHERE clause.
184
+
185
+ ```typescript
186
+ import { compileFilter } from '@truto/sqlite-builder'
187
+
188
+ const filter = {
189
+ status: 'ACTIVE',
190
+ age: { gte: 18, lt: 65 },
191
+ }
192
+
193
+ const result = compileFilter(filter)
194
+ // Returns: { text: '(("status" = ? AND "age" >= ? AND "age" < ?))', values: ['ACTIVE', 18, 65] }
195
+
196
+ // Use with the main sql template
197
+ const query = sql`
198
+ SELECT * FROM users
199
+ WHERE ${sql.raw(result.text)}
200
+ `
201
+ ```
202
+
203
+ ### Supported Operators
204
+
205
+ | Operator Family | JSON Form | SQL Fragment | Description |
206
+ | ------------------------- | ------------------------------------- | ---------------------------------------- | ----------------------------------- |
207
+ | **Equality** | `"field": value` | `"field" = ?` | Direct value comparison |
208
+ | **Inequality** | `"field": { "ne": value }` | `"field" <> ?` | Not equal comparison |
209
+ | **Comparison** | `"field": { "gt": value }` | `"field" > ?` | Greater than, gte, lt, lte |
210
+ | **Set Membership** | `"field": { "in": [1, 2, 3] }` | `"field" IN (?,?,?)` | Value in array |
211
+ | **Negative Set** | `"field": { "nin": [1, 2] }` | `"field" NOT IN (?,?)` | Value not in array |
212
+ | **NULL Checks** | `"field": { "exists": false }` | `"field" IS NULL` | Check for NULL/NOT NULL |
213
+ | **LIKE Patterns** | `"field": { "like": "john%" }` | `"field" LIKE ?` | Pattern matching |
214
+ | **Case-insensitive LIKE** | `"field": { "ilike": "%DOE%" }` | `"field" LIKE ? COLLATE NOCASE` | Case-insensitive patterns |
215
+ | **Regular Expressions** | `"field": { "regex": "^[A-Z]+" }` | `"field" REGEXP ?` | Regex patterns (requires extension) |
216
+ | **Logical AND** | `"and": [filter1, filter2]` | `(filter1 AND filter2)` | All conditions must match |
217
+ | **Logical OR** | `"or": [filter1, filter2]` | `(filter1 OR filter2)` | Any condition must match |
218
+ | **JSON Path** | `"profile.email": "test@example.com"` | `json_extract("profile", '$.email') = ?` | Query JSON column fields |
219
+
220
+ ### Filter Examples
221
+
222
+ #### Basic Operations
223
+
224
+ ```typescript
225
+ // Equality and comparison
226
+ const filter1 = {
227
+ status: 'ACTIVE',
228
+ age: { gte: 18, lt: 65 },
229
+ score: { gt: 80, lte: 100 },
230
+ }
231
+ // SQL: (("status" = ? AND "age" >= ? AND "age" < ? AND "score" > ? AND "score" <= ?))
232
+
233
+ // Set membership
234
+ const filter2 = {
235
+ role: { in: ['ADMIN', 'EDITOR'] },
236
+ department: { nin: ['ARCHIVED', 'DELETED'] },
237
+ }
238
+ // SQL: (("role" IN (?,?) AND "department" NOT IN (?,?)))
239
+
240
+ // NULL checks
241
+ const filter3 = {
242
+ email: { exists: true }, // IS NOT NULL
243
+ deleted_at: { exists: false }, // IS NULL
244
+ }
245
+ // SQL: (("email" IS NOT NULL AND "deleted_at" IS NULL))
246
+ ```
247
+
248
+ #### Pattern Matching
249
+
250
+ ```typescript
251
+ // LIKE patterns
252
+ const filter4 = {
253
+ username: { like: 'john%' }, // Starts with 'john'
254
+ email: { ilike: '%@EXAMPLE.COM%' }, // Case-insensitive contains
255
+ }
256
+ // SQL: (("username" LIKE ? AND "email" LIKE ? COLLATE NOCASE))
257
+
258
+ // Regular expressions (requires REGEXP extension)
259
+ const filter5 = {
260
+ email: { regex: '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$' },
261
+ phone: { regex: '^\\+1[0-9]{10}$' },
262
+ }
263
+ // SQL: (("email" REGEXP ? AND "phone" REGEXP ?))
264
+ ```
265
+
266
+ #### Logical Operators
267
+
268
+ ```typescript
269
+ // AND operator (explicit)
270
+ const filter6 = {
271
+ and: [
272
+ { status: 'ACTIVE' },
273
+ { age: { gte: 18 } },
274
+ { country: { in: ['US', 'CA', 'GB'] } },
275
+ ],
276
+ }
277
+ // SQL: ((("status" = ?) AND ("age" >= ?) AND ("country" IN (?,?,?))))
278
+
279
+ // OR operator
280
+ const filter7 = {
281
+ or: [
282
+ { age: { lt: 18 } }, // Minors
283
+ { age: { gte: 65 } }, // Seniors
284
+ ],
285
+ }
286
+ // SQL: ((("age" < ?) OR ("age" >= ?)))
287
+
288
+ // Mixed AND/OR logic
289
+ const filter8 = {
290
+ status: 'ACTIVE', // Implicit AND
291
+ or: [{ tags: { ilike: '%urgent%' } }, { priority: { gte: 8 } }],
292
+ }
293
+ // SQL: (((("tags" LIKE ? COLLATE NOCASE) OR ("priority" >= ?)) AND ("status" = ?)))
294
+ ```
295
+
296
+ #### JSON Path Querying
297
+
298
+ For SQLite JSON columns, use dot notation to query nested fields:
299
+
300
+ ```typescript
301
+ // Query JSON fields
302
+ const filter9 = {
303
+ 'profile.email': { regex: '.*@example\\.org$' },
304
+ 'profile.age': { gte: 21 },
305
+ 'settings.theme': { in: ['dark', 'light'] },
306
+ 'metadata.tags': { exists: true },
307
+ }
308
+ // SQL: ((json_extract("profile", '$.email') REGEXP ? AND
309
+ // json_extract("profile", '$.age') >= ? AND
310
+ // json_extract("settings", '$.theme') IN (?,?) AND
311
+ // json_extract("metadata", '$.tags') IS NOT NULL))
312
+
313
+ // Complex nested JSON query
314
+ const filter10 = {
315
+ and: [
316
+ { 'user.profile.email': { regex: '.*@company\\.com$' } },
317
+ { or: [{ 'user.role': 'ADMIN' }, { 'user.permissions.canEdit': true }] },
318
+ ],
319
+ }
320
+ ```
321
+
322
+ #### Kitchen Sink Examples
323
+
324
+ Real-world complex filters:
325
+
326
+ ```typescript
327
+ // Active users in specific regions, either minors/seniors or VIP
328
+ const complexFilter = {
329
+ and: [
330
+ { status: 'ACTIVE' },
331
+ { or: [{ age: { lt: 18 } }, { age: { gte: 65 } }, { membership: 'VIP' }] },
332
+ { country: { in: ['US', 'CA', 'GB'] } },
333
+ { email: { exists: true } },
334
+ { 'profile.verified': true },
335
+ ],
336
+ }
337
+
338
+ // Content filtering with multiple criteria
339
+ const contentFilter = {
340
+ name: { like: 'Project%' },
341
+ category: { nin: ['ARCHIVED', 'DELETED', 'SPAM'] },
342
+ created_at: { exists: true },
343
+ or: [
344
+ { tags: { ilike: '%important%' } },
345
+ { priority: { gte: 8 } },
346
+ { 'metadata.featured': true },
347
+ ],
348
+ }
349
+ ```
350
+
351
+ ### Integration with SQL Template
352
+
353
+ ```typescript
354
+ import { sql, compileFilter } from '@truto/sqlite-builder'
355
+
356
+ // Build the WHERE clause
357
+ const filter = {
358
+ status: 'ACTIVE',
359
+ age: { gte: 18 },
360
+ role: { in: ['USER', 'ADMIN'] },
361
+ }
362
+
363
+ const whereClause = compileFilter(filter)
364
+
365
+ // Use in complete query
366
+ const query = sql`
367
+ SELECT id, name, email, created_at
368
+ FROM users
369
+ WHERE ${sql.raw(whereClause.text)}
370
+ ORDER BY created_at DESC
371
+ LIMIT ${limit}
372
+ `
373
+
374
+ // Execute with driver
375
+ const results = db.prepare(query.text).all(...query.values)
376
+ ```
377
+
378
+ ### Security & Validation
379
+
380
+ The JSON filter compiler includes comprehensive security measures:
381
+
382
+ - **Operator validation**: Only known operators are allowed
383
+ - **Identifier safety**: Field names are validated using the same rules as `sql.ident()`
384
+ - **Array limits**: IN/NIN arrays limited to 999 items (SQLite limitation)
385
+ - **DoS protection**: Nesting depth ≤ 10, total operators ≤ 100
386
+ - **Type validation**: Strict type checking for all operator values
387
+ - **SQL injection prevention**: All values are parameterized
388
+
389
+ ```typescript
390
+ // ❌ These will throw errors
391
+ compileFilter({ age: { unknown: 18 } }) // Unknown operator
392
+ compileFilter({ 'user; DROP TABLE': 'value' }) // Invalid identifier
393
+ compileFilter({ role: { in: [] } }) // Empty array
394
+ compileFilter({ role: { in: new Array(1000).fill('x') } }) // Too large array
395
+
396
+ // ✅ These are safe and valid
397
+ compileFilter({ age: { gte: 18, lte: 65 } }) // Multiple operators
398
+ compileFilter({ 'profile.email': { exists: true } }) // JSON path
399
+ compileFilter({ or: [{ x: 1 }, { y: 2 }] }) // Logical operators
400
+ ```
401
+
402
+ ### REGEXP Extension
403
+
404
+ To use the `regex` operator, you need to load a REGEXP extension in SQLite:
405
+
406
+ ```typescript
407
+ // With better-sqlite3
408
+ import sqlite3 from 'better-sqlite3'
409
+
410
+ const db = new sqlite3('database.db')
411
+
412
+ // Load REGEXP extension (varies by implementation)
413
+ // This is implementation-specific - check your SQLite setup
414
+ db.loadExtension('regexp') // Example - actual method may vary
415
+
416
+ // Now regex filters work
417
+ const filter = {
418
+ email: { regex: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$' },
419
+ }
420
+ ```
421
+
174
422
  ## 🛡️ Security Model
175
423
 
176
424
  ### What's Protected
@@ -178,7 +426,8 @@ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
178
426
  - **SQL Injection**: All interpolated values are parameterized
179
427
  - **Stacked Queries**: Queries containing `;` followed by additional SQL are rejected
180
428
  - **Identifier Safety**: `sql.ident()` validates against ANSI identifier rules
181
- - **Length Limits**: Queries exceeding 100KB are rejected (configurable via `TRUTO_SQL_MAX_LENGTH`)
429
+ - **Length Limits**: Queries exceeding 100KB are rejected
430
+ - **Filter Security**: JSON filters validate operators, identifiers, and enforce limits
182
431
 
183
432
  ### What's Your Responsibility
184
433
 
@@ -186,6 +435,7 @@ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
186
435
  - **Validate identifiers before using `sql.ident()`** (though it has built-in validation)
187
436
  - **Use `sql.in()` instead of string concatenation** for arrays
188
437
  - **Keep your SQLite driver updated**
438
+ - **Load REGEXP extension safely** if using regex filters
189
439
 
190
440
  ### Supported Value Types
191
441
 
@@ -213,7 +463,7 @@ sql`SELECT * FROM users WHERE id = ${Symbol('test')}` // Unsupported type
213
463
  ### Basic CRUD Operations
214
464
 
215
465
  ```typescript
216
- import { sql } from 'truto-sqlite-builder'
466
+ import { sql } from '@truto/sqlite-builder'
217
467
 
218
468
  // CREATE with array identifiers
219
469
  const insertColumns = ['name', 'email', 'age']
@@ -274,6 +524,36 @@ const complexColumns = [
274
524
  const complexQuery = sql`SELECT ${sql.join(complexColumns)} FROM users`
275
525
  ```
276
526
 
527
+ ### Dynamic Queries with JSON Filters
528
+
529
+ ```typescript
530
+ import { sql, compileFilter } from '@truto/sqlite-builder'
531
+
532
+ // API endpoint that accepts JSON filter
533
+ app.get('/api/users', (req, res) => {
534
+ // User sends filter as JSON
535
+ const filter = req.body.filter || {}
536
+
537
+ // Safely compile to SQL
538
+ const whereClause = compileFilter(filter)
539
+
540
+ const query = sql`
541
+ SELECT id, name, email, created_at
542
+ FROM users
543
+ WHERE ${sql.raw(whereClause.text)}
544
+ ORDER BY created_at DESC
545
+ LIMIT ${req.query.limit || 20}
546
+ `
547
+
548
+ const users = db.prepare(query.text).all(...query.values)
549
+ res.json(users)
550
+ })
551
+
552
+ // Example API calls:
553
+ // POST /api/users { "filter": { "status": "ACTIVE", "age": { "gte": 18 } } }
554
+ // POST /api/users { "filter": { "or": [{ "role": "ADMIN" }, { "verified": true }] } }
555
+ ```
556
+
277
557
  ### Array Identifiers & Qualified Identifiers
278
558
 
279
559
  The `sql.ident()` function supports simple identifiers, qualified identifiers (table.column), arrays, and mixed arrays with SQL fragments:
@@ -395,17 +675,6 @@ insertUsers([
395
675
  ])
396
676
  ```
397
677
 
398
- ## ⚙️ Configuration
399
-
400
- ### Environment Variables
401
-
402
- - `TRUTO_SQL_MAX_LENGTH`: Maximum query length in bytes (default: 102400 = 100KB)
403
-
404
- ```bash
405
- # Increase query size limit to 1MB
406
- export TRUTO_SQL_MAX_LENGTH=1048576
407
- ```
408
-
409
678
  ## 🧪 Testing
410
679
 
411
680
  ```bash
@@ -0,0 +1,6 @@
1
+ import type { FilterResult, JsonFilter } from './types';
2
+ /**
3
+ * Compile a JSON filter to a SQL WHERE clause
4
+ */
5
+ export declare function compileFilter(filter: JsonFilter): FilterResult;
6
+ //# sourceMappingURL=filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,YAAY,EACZ,UAAU,EACX,MAAM,SAAS,CAAA;AA+UhB;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAa9D"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * truto-sqlite-builder - Safe, zero-dependency template-literal tag for SQLite queries
3
3
  */
4
+ export { compileFilter } from './filter';
4
5
  export { sql } from './sql';
5
- export type { SqlQuery } from './types';
6
+ export type { FilterResult, JsonFilter, SqlQuery } from './types';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAC3B,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA"}
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 CHANGED
@@ -1,143 +1,5 @@
1
- // node:process
2
- var C = Object.create;
3
- var T = Object.defineProperty;
4
- var q = Object.getOwnPropertyDescriptor;
5
- var A = Object.getOwnPropertyNames;
6
- var I = Object.getPrototypeOf;
7
- var Q = Object.prototype.hasOwnProperty;
8
- var S = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports);
9
- var N = (e, t) => {
10
- for (var n in t)
11
- T(e, n, { get: t[n], enumerable: true });
12
- };
13
- var d = (e, t, n, w) => {
14
- if (t && typeof t == "object" || typeof t == "function")
15
- for (let l of A(t))
16
- !Q.call(e, l) && l !== n && T(e, l, { get: () => t[l], enumerable: !(w = q(t, l)) || w.enumerable });
17
- return e;
18
- };
19
- var h = (e, t, n) => (d(e, t, "default"), n && d(n, t, "default"));
20
- var y = (e, t, n) => (n = e != null ? C(I(e)) : {}, d(t || !e || !e.__esModule ? T(n, "default", { value: e, enumerable: true }) : n, e));
21
- var v = S((B, E) => {
22
- var r = E.exports = {}, i, u;
23
- function p() {
24
- throw new Error("setTimeout has not been defined");
25
- }
26
- function g() {
27
- throw new Error("clearTimeout has not been defined");
28
- }
29
- (function() {
30
- try {
31
- typeof setTimeout == "function" ? i = setTimeout : i = p;
32
- } catch {
33
- i = p;
34
- }
35
- try {
36
- typeof clearTimeout == "function" ? u = clearTimeout : u = g;
37
- } catch {
38
- u = g;
39
- }
40
- })();
41
- function b(e) {
42
- if (i === setTimeout)
43
- return setTimeout(e, 0);
44
- if ((i === p || !i) && setTimeout)
45
- return i = setTimeout, setTimeout(e, 0);
46
- try {
47
- return i(e, 0);
48
- } catch {
49
- try {
50
- return i.call(null, e, 0);
51
- } catch {
52
- return i.call(this, e, 0);
53
- }
54
- }
55
- }
56
- function O(e) {
57
- if (u === clearTimeout)
58
- return clearTimeout(e);
59
- if ((u === g || !u) && clearTimeout)
60
- return u = clearTimeout, clearTimeout(e);
61
- try {
62
- return u(e);
63
- } catch {
64
- try {
65
- return u.call(null, e);
66
- } catch {
67
- return u.call(this, e);
68
- }
69
- }
70
- }
71
- var o = [], s = false, a, m = -1;
72
- function U() {
73
- !s || !a || (s = false, a.length ? o = a.concat(o) : m = -1, o.length && x());
74
- }
75
- function x() {
76
- if (!s) {
77
- var e = b(U);
78
- s = true;
79
- for (var t = o.length;t; ) {
80
- for (a = o, o = [];++m < t; )
81
- a && a[m].run();
82
- m = -1, t = o.length;
83
- }
84
- a = null, s = false, O(e);
85
- }
86
- }
87
- r.nextTick = function(e) {
88
- var t = new Array(arguments.length - 1);
89
- if (arguments.length > 1)
90
- for (var n = 1;n < arguments.length; n++)
91
- t[n - 1] = arguments[n];
92
- o.push(new L(e, t)), o.length === 1 && !s && b(x);
93
- };
94
- function L(e, t) {
95
- this.fun = e, this.array = t;
96
- }
97
- L.prototype.run = function() {
98
- this.fun.apply(null, this.array);
99
- };
100
- r.title = "browser";
101
- r.browser = true;
102
- r.env = {};
103
- r.argv = [];
104
- r.version = "";
105
- r.versions = {};
106
- function c() {}
107
- r.on = c;
108
- r.addListener = c;
109
- r.once = c;
110
- r.off = c;
111
- r.removeListener = c;
112
- r.removeAllListeners = c;
113
- r.emit = c;
114
- r.prependListener = c;
115
- r.prependOnceListener = c;
116
- r.listeners = function(e) {
117
- return [];
118
- };
119
- r.binding = function(e) {
120
- throw new Error("process.binding is not supported");
121
- };
122
- r.cwd = function() {
123
- return "/";
124
- };
125
- r.chdir = function(e) {
126
- throw new Error("process.chdir is not supported");
127
- };
128
- r.umask = function() {
129
- return 0;
130
- };
131
- });
132
- var f = {};
133
- N(f, { default: () => j });
134
- h(f, y(v()));
135
- var j = y(v());
136
-
137
1
  // src/sql.ts
138
- function getMaxQueryLength() {
139
- return parseInt(j.env.TRUTO_SQL_MAX_LENGTH || "102400", 10);
140
- }
2
+ var MAX_QUERY_LENGTH = 102400;
141
3
  var STACKED_QUERY_REGEX = /;[\s\S]*\S/;
142
4
  var QUALIFIED_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
143
5
  function formatDate(date) {
@@ -191,8 +53,8 @@ function sqlIdent(identifier) {
191
53
  throw new TypeError("Array items must be strings or SQL fragments");
192
54
  }
193
55
  }
194
- const text = fragments.map((f2) => f2.text).join(", ");
195
- const values = fragments.flatMap((f2) => [...f2.values]);
56
+ const text = fragments.map((f) => f.text).join(", ");
57
+ const values = fragments.flatMap((f) => [...f.values]);
196
58
  return {
197
59
  text,
198
60
  values
@@ -251,8 +113,8 @@ function sqlJoin(fragments, separator = ", ") {
251
113
  if (fragments.length === 0) {
252
114
  return { text: "", values: [] };
253
115
  }
254
- const text = fragments.map((f2) => f2.text).join(separator);
255
- const values = fragments.flatMap((f2) => [...f2.values]);
116
+ const text = fragments.map((f) => f.text).join(separator);
117
+ const values = fragments.flatMap((f) => [...f.values]);
256
118
  return { text, values };
257
119
  }
258
120
  function sql(strings, ...values) {
@@ -270,9 +132,8 @@ function sql(strings, ...values) {
270
132
  }
271
133
  text += strings[i + 1] || "";
272
134
  }
273
- const maxLength = getMaxQueryLength();
274
- if (text.length > maxLength) {
275
- throw new Error(`Query too long: ${text.length} bytes (max: ${maxLength})`);
135
+ if (text.length > MAX_QUERY_LENGTH) {
136
+ throw new Error(`Query too long: ${text.length} bytes (max: ${MAX_QUERY_LENGTH})`);
276
137
  }
277
138
  if (STACKED_QUERY_REGEX.test(text)) {
278
139
  throw new Error("Stacked queries are not allowed");
@@ -288,9 +149,246 @@ sql.in = sqlIn;
288
149
  sql.raw = sqlRaw;
289
150
  sql.blob = sqlBlob;
290
151
  sql.join = sqlJoin;
152
+
153
+ // src/filter.ts
154
+ var MAX_NESTING_DEPTH = 10;
155
+ var MAX_OPERATORS = 100;
156
+ var VALID_OPERATORS = new Set([
157
+ "gt",
158
+ "gte",
159
+ "lt",
160
+ "lte",
161
+ "ne",
162
+ "in",
163
+ "nin",
164
+ "like",
165
+ "ilike",
166
+ "regex",
167
+ "exists",
168
+ "and",
169
+ "or"
170
+ ]);
171
+ function isJsonPath(field) {
172
+ return field.includes(".");
173
+ }
174
+ function compileJsonPath(field) {
175
+ const parts = field.split(".");
176
+ const columnName = parts[0];
177
+ const jsonPath = parts.slice(1);
178
+ if (!columnName || jsonPath.length === 0 || jsonPath.some((part) => !part)) {
179
+ throw new SyntaxError(`Invalid JSON path: ${field}`);
180
+ }
181
+ return {
182
+ columnName,
183
+ jsonPath: "$." + jsonPath.join(".")
184
+ };
185
+ }
186
+ function compileFieldCondition(field, condition, context) {
187
+ if (condition === null || condition === undefined || typeof condition === "string" || typeof condition === "number" || typeof condition === "boolean" || condition instanceof Date) {
188
+ context.operatorCount++;
189
+ if (context.operatorCount > MAX_OPERATORS) {
190
+ throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
191
+ }
192
+ if (isJsonPath(field)) {
193
+ const { columnName, jsonPath } = compileJsonPath(field);
194
+ const identFragment2 = sql.ident(columnName);
195
+ if (condition === null || condition === undefined) {
196
+ context.values.push(jsonPath);
197
+ return `(json_extract(${identFragment2.text}, ?) IS NULL)`;
198
+ } else {
199
+ context.values.push(jsonPath, sql.value(condition));
200
+ return `(json_extract(${identFragment2.text}, ?) = ?)`;
201
+ }
202
+ } else {
203
+ const identFragment2 = sql.ident(field);
204
+ if (condition === null || condition === undefined) {
205
+ return `(${identFragment2.text} IS NULL)`;
206
+ } else {
207
+ context.values.push(sql.value(condition));
208
+ return `(${identFragment2.text} = ?)`;
209
+ }
210
+ }
211
+ }
212
+ if (typeof condition !== "object" || condition === null) {
213
+ throw new TypeError("Condition must be a value or operator object");
214
+ }
215
+ const operators = condition;
216
+ const clauses = [];
217
+ for (const op of Object.keys(operators)) {
218
+ if (!VALID_OPERATORS.has(op)) {
219
+ throw new SyntaxError(`Unknown operator: ${op}`);
220
+ }
221
+ }
222
+ const identFragment = sql.ident(isJsonPath(field) ? compileJsonPath(field).columnName : field);
223
+ const fieldExpr = isJsonPath(field) ? `json_extract(${identFragment.text}, ?)` : identFragment.text;
224
+ if (isJsonPath(field)) {
225
+ context.values.push(compileJsonPath(field).jsonPath);
226
+ }
227
+ if ("exists" in operators) {
228
+ context.operatorCount++;
229
+ if (context.operatorCount > MAX_OPERATORS) {
230
+ throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
231
+ }
232
+ return operators.exists ? `(${fieldExpr} IS NOT NULL)` : `(${fieldExpr} IS NULL)`;
233
+ }
234
+ for (const [op, value] of Object.entries(operators)) {
235
+ context.operatorCount++;
236
+ if (context.operatorCount > MAX_OPERATORS) {
237
+ throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
238
+ }
239
+ switch (op) {
240
+ case "gt":
241
+ context.values.push(sql.value(value));
242
+ clauses.push(`${fieldExpr} > ?`);
243
+ break;
244
+ case "gte":
245
+ context.values.push(sql.value(value));
246
+ clauses.push(`${fieldExpr} >= ?`);
247
+ break;
248
+ case "lt":
249
+ context.values.push(sql.value(value));
250
+ clauses.push(`${fieldExpr} < ?`);
251
+ break;
252
+ case "lte":
253
+ context.values.push(sql.value(value));
254
+ clauses.push(`${fieldExpr} <= ?`);
255
+ break;
256
+ case "ne":
257
+ if (value === null || value === undefined) {
258
+ clauses.push(`${fieldExpr} IS NOT NULL`);
259
+ } else {
260
+ context.values.push(sql.value(value));
261
+ clauses.push(`${fieldExpr} <> ?`);
262
+ }
263
+ break;
264
+ case "in": {
265
+ if (!Array.isArray(value)) {
266
+ throw new TypeError("IN operator requires an array");
267
+ }
268
+ if (value.length === 0) {
269
+ throw new TypeError("IN operator cannot be used with empty arrays");
270
+ }
271
+ if (value.length > 999) {
272
+ throw new RangeError("IN operator cannot be used with arrays larger than 999 items");
273
+ }
274
+ const inFragment = sql.in(value);
275
+ context.values.push(...inFragment.values);
276
+ clauses.push(`${fieldExpr} IN ${inFragment.text}`);
277
+ break;
278
+ }
279
+ case "nin": {
280
+ if (!Array.isArray(value)) {
281
+ throw new TypeError("NIN operator requires an array");
282
+ }
283
+ if (value.length === 0) {
284
+ throw new TypeError("NIN operator cannot be used with empty arrays");
285
+ }
286
+ if (value.length > 999) {
287
+ throw new RangeError("NIN operator cannot be used with arrays larger than 999 items");
288
+ }
289
+ const ninFragment = sql.in(value);
290
+ context.values.push(...ninFragment.values);
291
+ clauses.push(`${fieldExpr} NOT IN ${ninFragment.text}`);
292
+ break;
293
+ }
294
+ case "like":
295
+ if (typeof value !== "string") {
296
+ throw new TypeError("LIKE operator requires a string pattern");
297
+ }
298
+ context.values.push(value);
299
+ clauses.push(`${fieldExpr} LIKE ?`);
300
+ break;
301
+ case "ilike":
302
+ if (typeof value !== "string") {
303
+ throw new TypeError("ILIKE operator requires a string pattern");
304
+ }
305
+ context.values.push(value);
306
+ clauses.push(`${fieldExpr} LIKE ? COLLATE NOCASE`);
307
+ break;
308
+ case "regex":
309
+ if (typeof value !== "string") {
310
+ throw new TypeError("REGEX operator requires a string pattern");
311
+ }
312
+ context.values.push(value);
313
+ clauses.push(`${fieldExpr} REGEXP ?`);
314
+ break;
315
+ }
316
+ }
317
+ if (clauses.length === 0) {
318
+ throw new SyntaxError("Operator object must contain at least one valid operator");
319
+ }
320
+ return clauses.length === 1 ? `(${clauses[0]})` : `(${clauses.join(" AND ")})`;
321
+ }
322
+ function compileFilterRecursive(filter, context) {
323
+ if (context.depth >= MAX_NESTING_DEPTH) {
324
+ throw new RangeError(`Nesting depth too deep (max: ${MAX_NESTING_DEPTH})`);
325
+ }
326
+ if (typeof filter !== "object" || filter === null) {
327
+ throw new TypeError("Filter must be an object");
328
+ }
329
+ context.depth++;
330
+ const clauses = [];
331
+ if ("and" in filter && filter.and) {
332
+ context.operatorCount++;
333
+ if (context.operatorCount > MAX_OPERATORS) {
334
+ throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
335
+ }
336
+ if (!Array.isArray(filter.and)) {
337
+ throw new TypeError("AND operator must be an array");
338
+ }
339
+ if (filter.and.length === 0) {
340
+ throw new TypeError("AND operator cannot be used with empty arrays");
341
+ }
342
+ const andClauses = filter.and.map((subFilter) => compileFilterRecursive(subFilter, context));
343
+ clauses.push(`(${andClauses.join(" AND ")})`);
344
+ }
345
+ if ("or" in filter && filter.or) {
346
+ context.operatorCount++;
347
+ if (context.operatorCount > MAX_OPERATORS) {
348
+ throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
349
+ }
350
+ if (!Array.isArray(filter.or)) {
351
+ throw new TypeError("OR operator must be an array");
352
+ }
353
+ if (filter.or.length === 0) {
354
+ throw new TypeError("OR operator cannot be used with empty arrays");
355
+ }
356
+ const orClauses = filter.or.map((subFilter) => compileFilterRecursive(subFilter, context));
357
+ clauses.push(`(${orClauses.join(" OR ")})`);
358
+ }
359
+ const fieldEntries = Object.entries(filter).filter(([field, condition]) => field !== "and" && field !== "or" && condition !== undefined);
360
+ for (const [field, condition] of fieldEntries) {
361
+ if (Array.isArray(condition)) {
362
+ throw new SyntaxError(`Field '${field}' cannot have array value. Use logical operators 'and'/'or' instead.`);
363
+ }
364
+ clauses.push(compileFieldCondition(field, condition, context));
365
+ }
366
+ context.depth--;
367
+ if (clauses.length === 0) {
368
+ throw new SyntaxError("Filter must contain at least one condition");
369
+ }
370
+ if (clauses.length === 1) {
371
+ return clauses[0];
372
+ } else {
373
+ return `(${clauses.join(" AND ")})`;
374
+ }
375
+ }
376
+ function compileFilter(filter) {
377
+ const context = {
378
+ depth: 0,
379
+ operatorCount: 0,
380
+ values: []
381
+ };
382
+ const text = compileFilterRecursive(filter, context);
383
+ return Object.freeze({
384
+ text: `(${text})`,
385
+ values: Object.freeze([...context.values])
386
+ });
387
+ }
291
388
  export {
292
- sql
389
+ sql,
390
+ compileFilter
293
391
  };
294
392
 
295
- //# debugId=9115DAB11403208364756E2164756E21
393
+ //# debugId=62BC5523B4B64F9C64756E2164756E21
296
394
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["node:process", "../src/sql.ts"],
3
+ "sources": ["../src/sql.ts", "../src/filter.ts"],
4
4
  "sourcesContent": [
5
- "var C=Object.create;var T=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var I=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var S=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),N=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},d=(e,t,n,w)=>{if(t&&typeof t==\"object\"||typeof t==\"function\")for(let l of A(t))!Q.call(e,l)&&l!==n&&T(e,l,{get:()=>t[l],enumerable:!(w=q(t,l))||w.enumerable});return e},h=(e,t,n)=>(d(e,t,\"default\"),n&&d(n,t,\"default\")),y=(e,t,n)=>(n=e!=null?C(I(e)):{},d(t||!e||!e.__esModule?T(n,\"default\",{value:e,enumerable:!0}):n,e));var v=S((B,E)=>{var r=E.exports={},i,u;function p(){throw new Error(\"setTimeout has not been defined\")}function g(){throw new Error(\"clearTimeout has not been defined\")}(function(){try{typeof setTimeout==\"function\"?i=setTimeout:i=p}catch{i=p}try{typeof clearTimeout==\"function\"?u=clearTimeout:u=g}catch{u=g}})();function b(e){if(i===setTimeout)return setTimeout(e,0);if((i===p||!i)&&setTimeout)return i=setTimeout,setTimeout(e,0);try{return i(e,0)}catch{try{return i.call(null,e,0)}catch{return i.call(this,e,0)}}}function O(e){if(u===clearTimeout)return clearTimeout(e);if((u===g||!u)&&clearTimeout)return u=clearTimeout,clearTimeout(e);try{return u(e)}catch{try{return u.call(null,e)}catch{return u.call(this,e)}}}var o=[],s=!1,a,m=-1;function U(){!s||!a||(s=!1,a.length?o=a.concat(o):m=-1,o.length&&x())}function x(){if(!s){var e=b(U);s=!0;for(var t=o.length;t;){for(a=o,o=[];++m<t;)a&&a[m].run();m=-1,t=o.length}a=null,s=!1,O(e)}}r.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];o.push(new L(e,t)),o.length===1&&!s&&b(x)};function L(e,t){this.fun=e,this.array=t}L.prototype.run=function(){this.fun.apply(null,this.array)};r.title=\"browser\";r.browser=!0;r.env={};r.argv=[];r.version=\"\";r.versions={};function c(){}r.on=c;r.addListener=c;r.once=c;r.off=c;r.removeListener=c;r.removeAllListeners=c;r.emit=c;r.prependListener=c;r.prependOnceListener=c;r.listeners=function(e){return[]};r.binding=function(e){throw new Error(\"process.binding is not supported\")};r.cwd=function(){return\"/\"};r.chdir=function(e){throw new Error(\"process.chdir is not supported\")};r.umask=function(){return 0}});var f={};N(f,{default:()=>j});h(f,y(v()));var j=y(v());export{j as default};\n",
6
- "import process from 'node:process'\nimport type { SqlFragment, SqlQuery, SqlValue } from './types'\n\n// Get max query length from environment (default 100KB)\nfunction getMaxQueryLength(): number {\n return parseInt(process.env.TRUTO_SQL_MAX_LENGTH || '102400', 10)\n}\n\n// Regex to detect stacked queries (semicolon followed by non-whitespace)\nconst STACKED_QUERY_REGEX = /;[\\s\\S]*\\S/\n\n// ANSI identifier validation - supports qualified identifiers (e.g., table.column)\nconst QUALIFIED_IDENTIFIER_REGEX =\n /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$/\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 * 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 // Handle SqlFragment objects (like sql.raw())\n if (\n item &&\n typeof item === 'object' &&\n 'text' in item &&\n 'values' in item &&\n typeof (item as Record<string, unknown>).text === 'string' &&\n Array.isArray((item as Record<string, unknown>).values)\n ) {\n fragments.push(item as SqlFragment)\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({\n text: '\"' + item + '\"',\n values: [],\n })\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 {\n text,\n values,\n }\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 {\n text: '\"' + identifier + '\"',\n values: [],\n }\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 {\n text: `(${placeholders})`,\n values,\n }\n}\n\n/**\n * Create raw SQL fragment (DANGEROUS - must not contain user input)\n */\nfunction sqlRaw(rawSql: string): SqlFragment {\n if (typeof rawSql !== 'string') {\n throw new TypeError('sql.raw() requires a string')\n }\n\n return {\n text: rawSql,\n values: [],\n }\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 {\n text: '?',\n values: [data],\n }\n}\n\n/**\n * Join SQL fragments with a separator\n */\nfunction sqlJoin(\n fragments: readonly SqlFragment[],\n separator = ', ',\n): SqlFragment {\n if (!Array.isArray(fragments)) {\n throw new TypeError('sql.join() requires an array of fragments')\n }\n\n if (fragments.length === 0) {\n return { text: '', values: [] }\n }\n\n const text = fragments.map((f: SqlFragment) => f.text).join(separator)\n const values = fragments.flatMap((f: SqlFragment) => [...f.values])\n\n return { text, values }\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 // Handle SqlFragment objects (from helper functions)\n if (\n value &&\n typeof value === 'object' &&\n 'text' in value &&\n 'values' in value &&\n typeof (value as Record<string, unknown>).text === 'string' &&\n Array.isArray((value as Record<string, unknown>).values)\n ) {\n const fragment = value as SqlFragment\n text += fragment.text\n queryValues.push(...fragment.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 const maxLength = getMaxQueryLength()\n if (text.length > maxLength) {\n throw new Error(`Query too long: ${text.length} bytes (max: ${maxLength})`)\n }\n\n if (STACKED_QUERY_REGEX.test(text)) {\n throw new Error('Stacked queries are not allowed')\n }\n\n // Return frozen result\n return Object.freeze({\n text,\n values: Object.freeze([...queryValues]),\n })\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"
5
+ "import type { SqlFragment, SqlQuery, SqlValue } from './types'\n\n// Maximum query length (100KB)\nconst MAX_QUERY_LENGTH = 102400\n\n// Regex to detect stacked queries (semicolon followed by non-whitespace)\nconst STACKED_QUERY_REGEX = /;[\\s\\S]*\\S/\n\n// ANSI identifier validation - supports qualified identifiers (e.g., table.column)\nconst QUALIFIED_IDENTIFIER_REGEX =\n /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$/\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 * 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 // Handle SqlFragment objects (like sql.raw())\n if (\n item &&\n typeof item === 'object' &&\n 'text' in item &&\n 'values' in item &&\n typeof (item as Record<string, unknown>).text === 'string' &&\n Array.isArray((item as Record<string, unknown>).values)\n ) {\n fragments.push(item as SqlFragment)\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({\n text: '\"' + item + '\"',\n values: [],\n })\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 {\n text,\n values,\n }\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 {\n text: '\"' + identifier + '\"',\n values: [],\n }\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 {\n text: `(${placeholders})`,\n values,\n }\n}\n\n/**\n * Create raw SQL fragment (DANGEROUS - must not contain user input)\n */\nfunction sqlRaw(rawSql: string): SqlFragment {\n if (typeof rawSql !== 'string') {\n throw new TypeError('sql.raw() requires a string')\n }\n\n return {\n text: rawSql,\n values: [],\n }\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 {\n text: '?',\n values: [data],\n }\n}\n\n/**\n * Join SQL fragments with a separator\n */\nfunction sqlJoin(\n fragments: readonly SqlFragment[],\n separator = ', ',\n): SqlFragment {\n if (!Array.isArray(fragments)) {\n throw new TypeError('sql.join() requires an array of fragments')\n }\n\n if (fragments.length === 0) {\n return { text: '', values: [] }\n }\n\n const text = fragments.map((f: SqlFragment) => f.text).join(separator)\n const values = fragments.flatMap((f: SqlFragment) => [...f.values])\n\n return { text, values }\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 // Handle SqlFragment objects (from helper functions)\n if (\n value &&\n typeof value === 'object' &&\n 'text' in value &&\n 'values' in value &&\n typeof (value as Record<string, unknown>).text === 'string' &&\n Array.isArray((value as Record<string, unknown>).values)\n ) {\n const fragment = value as SqlFragment\n text += fragment.text\n queryValues.push(...fragment.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 if (STACKED_QUERY_REGEX.test(text)) {\n throw new Error('Stacked queries are not allowed')\n }\n\n // Return frozen result\n return Object.freeze({\n text,\n values: Object.freeze([...queryValues]),\n })\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",
6
+ "import { 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 * Compile a single field condition to SQL\n */\nfunction compileFieldCondition(\n field: string,\n condition: FieldCondition,\n context: CompileContext,\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\n if (condition === null || condition === undefined) {\n context.values.push(jsonPath)\n return `(json_extract(${identFragment.text}, ?) IS NULL)`\n } else {\n context.values.push(jsonPath, sql.value(condition))\n return `(json_extract(${identFragment.text}, ?) = ?)`\n }\n } else {\n const identFragment = sql.ident(field)\n\n if (condition === null || condition === undefined) {\n return `(${identFragment.text} IS NULL)`\n } else {\n context.values.push(sql.value(condition))\n return `(${identFragment.text} = ?)`\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 fieldExpr = isJsonPath(field)\n ? `json_extract(${identFragment.text}, ?)`\n : identFragment.text\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 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 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 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): 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),\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),\n )\n clauses.push(`(${orClauses.join(' OR ')})`)\n }\n\n // Handle field conditions (implicit AND) - preserve original order\n const fieldEntries = Object.entries(filter).filter(\n ([field, condition]) =>\n field !== 'and' && field !== 'or' && 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),\n )\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 return Object.freeze({\n text: `(${text})`,\n values: Object.freeze([...context.values]),\n })\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "9115DAB11403208364756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "62BC5523B4B64F9C64756E2164756E21",
10
10
  "names": []
11
11
  }
package/dist/sql.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AA4B9D;;GAEG;AACH,iBAAS,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAwB1C;AAED;;GAEG;AACH,iBAAS,QAAQ,CACf,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC,EAAE,GACrD,WAAW,CAkEb;AAED;;GAEG;AACH,iBAAS,KAAK,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,WAAW,CAuBrD;AAED;;GAEG;AACH,iBAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAS3C;AAED;;GAEG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,CASvD;AAED;;GAEG;AACH,iBAAS,OAAO,CACd,SAAS,EAAE,SAAS,WAAW,EAAE,EACjC,SAAS,SAAO,GACf,WAAW,CAab;AAED;;GAEG;AACH,iBAAS,GAAG,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CA4C1E;kBA5CQ,GAAG;;;;;;;;;AAsDZ,OAAO,EAAE,GAAG,EAAE,CAAA"}
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AA0B9D;;GAEG;AACH,iBAAS,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAwB1C;AAED;;GAEG;AACH,iBAAS,QAAQ,CACf,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC,EAAE,GACrD,WAAW,CAkEb;AAED;;GAEG;AACH,iBAAS,KAAK,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,WAAW,CAuBrD;AAED;;GAEG;AACH,iBAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAS3C;AAED;;GAEG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,CASvD;AAED;;GAEG;AACH,iBAAS,OAAO,CACd,SAAS,EAAE,SAAS,WAAW,EAAE,EACjC,SAAS,SAAO,GACf,WAAW,CAab;AAED;;GAEG;AACH,iBAAS,GAAG,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CA6C1E;kBA7CQ,GAAG;;;;;;;;;AAuDZ,OAAO,EAAE,GAAG,EAAE,CAAA"}
package/dist/types.d.ts CHANGED
@@ -16,4 +16,58 @@ export interface SqlFragment {
16
16
  readonly text: string;
17
17
  readonly values: readonly unknown[];
18
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, or explicit logical operators
62
+ */
63
+ export type JsonFilter = LogicalOperators & {
64
+ [field: string]: FieldCondition | readonly JsonFilter[] | undefined;
65
+ };
66
+ /**
67
+ * Result of compiling a JSON filter to SQL
68
+ */
69
+ export interface FilterResult {
70
+ readonly text: string;
71
+ readonly values: readonly unknown[];
72
+ }
19
73
  //# sourceMappingURL=types.d.ts.map
@@ -1 +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"}
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;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG;IAC1C,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,UAAU,EAAE,GAAG,SAAS,CAAA;CACpE,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,6 +1,6 @@
1
1
  {
2
2
  "name": "@truto/sqlite-builder",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Safe, zero-dependency template-literal tag for SQLite queries in any JS environment",
5
5
  "keywords": [
6
6
  "sqlite",