@truto/sqlite-builder 1.0.1 → 1.0.3
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 +293 -12
- package/dist/filter.d.ts +6 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -4
- package/dist/index.js.map +5 -4
- package/dist/sql.d.ts.map +1 -1
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- 🌍 **Universal**: Works in Bun, Node.js, Deno, and modern browsers
|
|
18
18
|
- 🎯 **TypeScript-first**: Full type safety with excellent IDE support
|
|
19
19
|
- 🔧 **Helper functions**: Built-in utilities for identifiers, IN clauses, and more
|
|
20
|
+
- 🔍 **JSON Filter Language**: MongoDB-style JSON filters for WHERE clauses
|
|
20
21
|
- ⚡ **Lightweight**: Minimal bundle size with tree-shaking support
|
|
21
22
|
|
|
22
23
|
## 📦 Installation
|
|
@@ -41,7 +42,7 @@ pnpm add @truto/sqlite-builder
|
|
|
41
42
|
|
|
42
43
|
```typescript
|
|
43
44
|
import sqlite3 from 'better-sqlite3'
|
|
44
|
-
import { sql } from '@truto/sqlite-builder'
|
|
45
|
+
import { sql, compileFilter } from '@truto/sqlite-builder'
|
|
45
46
|
|
|
46
47
|
const db = new sqlite3('database.db')
|
|
47
48
|
|
|
@@ -50,14 +51,17 @@ const name = 'Alice'
|
|
|
50
51
|
const { text, values } = sql`SELECT * FROM users WHERE name = ${name}`
|
|
51
52
|
const users = db.prepare(text).all(...values)
|
|
52
53
|
|
|
53
|
-
//
|
|
54
|
-
const
|
|
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)
|
|
55
62
|
const query = sql`
|
|
56
|
-
SELECT
|
|
57
|
-
|
|
58
|
-
JOIN ${sql.ident('posts')} p ON u.id = p.user_id
|
|
59
|
-
WHERE u.id IN ${sql.in(userIds)}
|
|
60
|
-
AND u.status = ${'active'}
|
|
63
|
+
SELECT * FROM users
|
|
64
|
+
WHERE ${sql.raw(whereText)}
|
|
61
65
|
`
|
|
62
66
|
|
|
63
67
|
const results = db.prepare(query.text).all(...query.values)
|
|
@@ -91,7 +95,7 @@ const query = sql`SELECT * FROM ${sql.ident(table)}`
|
|
|
91
95
|
|
|
92
96
|
// Qualified identifiers (table.column)
|
|
93
97
|
const qualifiedQuery = sql`SELECT ${sql.ident('u.name')}, ${sql.ident('u.email')} FROM users u`
|
|
94
|
-
// Returns: { text: 'SELECT "u.name", "u.email" FROM users u', values: [] }
|
|
98
|
+
// Returns: { text: 'SELECT "u"."name", "u"."email" FROM users u', values: [] }
|
|
95
99
|
|
|
96
100
|
// Array of identifiers (useful for column lists)
|
|
97
101
|
const columns = ['name', 'email', 'created_at']
|
|
@@ -101,7 +105,7 @@ const selectQuery = sql`SELECT ${sql.ident(columns)} FROM users`
|
|
|
101
105
|
// Mixed arrays with qualified and simple identifiers
|
|
102
106
|
const mixedColumns = ['u.id', 'name', 'u.email', 'p.title']
|
|
103
107
|
const joinQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users u JOIN posts p ON u.id = p.user_id`
|
|
104
|
-
// Returns: { text: 'SELECT "u.id", "name", "u.email", "p.title" FROM users u JOIN posts p ON u.id = p.user_id', values: [] }
|
|
108
|
+
// Returns: { text: 'SELECT "u"."id", "name", "u"."email", "p"."title" FROM users u JOIN posts p ON u.id = p.user_id', values: [] }
|
|
105
109
|
|
|
106
110
|
// Mixed arrays with identifiers and SQL fragments
|
|
107
111
|
const mixedColumns = ['id', 'name', sql.raw('UPPER(email) as email_upper')]
|
|
@@ -170,6 +174,251 @@ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
|
|
|
170
174
|
// Returns: { text: "SELECT * FROM users WHERE name = ? AND age = ? AND active = ?", values: ['John', 30, true] }
|
|
171
175
|
```
|
|
172
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
|
+
|
|
173
422
|
## 🛡️ Security Model
|
|
174
423
|
|
|
175
424
|
### What's Protected
|
|
@@ -178,6 +427,7 @@ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
|
|
|
178
427
|
- **Stacked Queries**: Queries containing `;` followed by additional SQL are rejected
|
|
179
428
|
- **Identifier Safety**: `sql.ident()` validates against ANSI identifier rules
|
|
180
429
|
- **Length Limits**: Queries exceeding 100KB are rejected
|
|
430
|
+
- **Filter Security**: JSON filters validate operators, identifiers, and enforce limits
|
|
181
431
|
|
|
182
432
|
### What's Your Responsibility
|
|
183
433
|
|
|
@@ -185,6 +435,7 @@ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
|
|
|
185
435
|
- **Validate identifiers before using `sql.ident()`** (though it has built-in validation)
|
|
186
436
|
- **Use `sql.in()` instead of string concatenation** for arrays
|
|
187
437
|
- **Keep your SQLite driver updated**
|
|
438
|
+
- **Load REGEXP extension safely** if using regex filters
|
|
188
439
|
|
|
189
440
|
### Supported Value Types
|
|
190
441
|
|
|
@@ -273,6 +524,36 @@ const complexColumns = [
|
|
|
273
524
|
const complexQuery = sql`SELECT ${sql.join(complexColumns)} FROM users`
|
|
274
525
|
```
|
|
275
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
|
+
|
|
276
557
|
### Array Identifiers & Qualified Identifiers
|
|
277
558
|
|
|
278
559
|
The `sql.ident()` function supports simple identifiers, qualified identifiers (table.column), arrays, and mixed arrays with SQL fragments:
|
|
@@ -286,7 +567,7 @@ const simpleQuery = sql`SELECT ${sql.ident(column)} FROM ${sql.ident(table)}`
|
|
|
286
567
|
|
|
287
568
|
// ✅ Qualified identifiers (table.column)
|
|
288
569
|
const qualifiedQuery = sql`SELECT ${sql.ident('u.name')}, ${sql.ident('p.title')} FROM users u JOIN posts p ON u.id = p.user_id`
|
|
289
|
-
// Result: SELECT "u.name", "p.title" FROM users u JOIN posts p ON u.id = p.user_id
|
|
570
|
+
// Result: SELECT "u"."name", "p"."title" FROM users u JOIN posts p ON u.id = p.user_id
|
|
290
571
|
|
|
291
572
|
// ✅ Pure identifier arrays (clean and concise)
|
|
292
573
|
const columns = ['id', 'name', 'email', 'created_at']
|
|
@@ -296,7 +577,7 @@ const arrayQuery = sql`SELECT ${sql.ident(columns)} FROM users`
|
|
|
296
577
|
// ✅ Mixed qualified and simple identifiers in arrays
|
|
297
578
|
const mixedColumns = ['u.id', 'name', 'u.email', 'p.title', 'created_at']
|
|
298
579
|
const mixedQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users u LEFT JOIN posts p ON u.id = p.user_id`
|
|
299
|
-
// Result: SELECT "u.id", "name", "u.email", "p.title", "created_at" FROM users u LEFT JOIN posts p ON u.id = p.user_id
|
|
580
|
+
// Result: SELECT "u"."id", "name", "u"."email", "p"."title", "created_at" FROM users u LEFT JOIN posts p ON u.id = p.user_id
|
|
300
581
|
|
|
301
582
|
// ✅ NEW: Mixed arrays with identifiers and SQL fragments
|
|
302
583
|
const mixedColumns = [
|
package/dist/filter.d.ts
ADDED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var MAX_QUERY_LENGTH = 102400;
|
|
3
3
|
var STACKED_QUERY_REGEX = /;[\s\S]*\S/;
|
|
4
4
|
var QUALIFIED_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
5
|
+
var SIMPLE_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5
6
|
function formatDate(date) {
|
|
6
7
|
const year = date.getFullYear();
|
|
7
8
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
@@ -29,6 +30,19 @@ function sqlValue(value) {
|
|
|
29
30
|
}
|
|
30
31
|
throw new TypeError(`Unsupported value type: ${typeof value}`);
|
|
31
32
|
}
|
|
33
|
+
function quoteSingleIdentifier(identifier) {
|
|
34
|
+
if (!SIMPLE_IDENTIFIER_REGEX.test(identifier)) {
|
|
35
|
+
throw new TypeError(`Invalid identifier part: ${identifier}. Must be a valid ANSI identifier.`);
|
|
36
|
+
}
|
|
37
|
+
return `"${identifier}"`;
|
|
38
|
+
}
|
|
39
|
+
function quoteQualifiedIdentifier(identifier) {
|
|
40
|
+
if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {
|
|
41
|
+
throw new TypeError(`Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
42
|
+
}
|
|
43
|
+
const parts = identifier.split(".");
|
|
44
|
+
return parts.map(quoteSingleIdentifier).join(".");
|
|
45
|
+
}
|
|
32
46
|
function sqlIdent(identifier) {
|
|
33
47
|
if (Array.isArray(identifier)) {
|
|
34
48
|
if (identifier.length === 0) {
|
|
@@ -46,7 +60,7 @@ function sqlIdent(identifier) {
|
|
|
46
60
|
throw new TypeError(`Invalid identifier: ${item}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
47
61
|
}
|
|
48
62
|
fragments.push({
|
|
49
|
-
text:
|
|
63
|
+
text: quoteQualifiedIdentifier(item),
|
|
50
64
|
values: []
|
|
51
65
|
});
|
|
52
66
|
} else {
|
|
@@ -67,7 +81,7 @@ function sqlIdent(identifier) {
|
|
|
67
81
|
throw new TypeError(`Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
68
82
|
}
|
|
69
83
|
return {
|
|
70
|
-
text:
|
|
84
|
+
text: quoteQualifiedIdentifier(identifier),
|
|
71
85
|
values: []
|
|
72
86
|
};
|
|
73
87
|
}
|
|
@@ -149,9 +163,246 @@ sql.in = sqlIn;
|
|
|
149
163
|
sql.raw = sqlRaw;
|
|
150
164
|
sql.blob = sqlBlob;
|
|
151
165
|
sql.join = sqlJoin;
|
|
166
|
+
|
|
167
|
+
// src/filter.ts
|
|
168
|
+
var MAX_NESTING_DEPTH = 10;
|
|
169
|
+
var MAX_OPERATORS = 100;
|
|
170
|
+
var VALID_OPERATORS = new Set([
|
|
171
|
+
"gt",
|
|
172
|
+
"gte",
|
|
173
|
+
"lt",
|
|
174
|
+
"lte",
|
|
175
|
+
"ne",
|
|
176
|
+
"in",
|
|
177
|
+
"nin",
|
|
178
|
+
"like",
|
|
179
|
+
"ilike",
|
|
180
|
+
"regex",
|
|
181
|
+
"exists",
|
|
182
|
+
"and",
|
|
183
|
+
"or"
|
|
184
|
+
]);
|
|
185
|
+
function isJsonPath(field) {
|
|
186
|
+
return field.includes(".");
|
|
187
|
+
}
|
|
188
|
+
function compileJsonPath(field) {
|
|
189
|
+
const parts = field.split(".");
|
|
190
|
+
const columnName = parts[0];
|
|
191
|
+
const jsonPath = parts.slice(1);
|
|
192
|
+
if (!columnName || jsonPath.length === 0 || jsonPath.some((part) => !part)) {
|
|
193
|
+
throw new SyntaxError(`Invalid JSON path: ${field}`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
columnName,
|
|
197
|
+
jsonPath: "$." + jsonPath.join(".")
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function compileFieldCondition(field, condition, context) {
|
|
201
|
+
if (condition === null || condition === undefined || typeof condition === "string" || typeof condition === "number" || typeof condition === "boolean" || condition instanceof Date) {
|
|
202
|
+
context.operatorCount++;
|
|
203
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
204
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
205
|
+
}
|
|
206
|
+
if (isJsonPath(field)) {
|
|
207
|
+
const { columnName, jsonPath } = compileJsonPath(field);
|
|
208
|
+
const identFragment2 = sql.ident(columnName);
|
|
209
|
+
if (condition === null || condition === undefined) {
|
|
210
|
+
context.values.push(jsonPath);
|
|
211
|
+
return `(json_extract(${identFragment2.text}, ?) IS NULL)`;
|
|
212
|
+
} else {
|
|
213
|
+
context.values.push(jsonPath, sql.value(condition));
|
|
214
|
+
return `(json_extract(${identFragment2.text}, ?) = ?)`;
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
const identFragment2 = sql.ident(field);
|
|
218
|
+
if (condition === null || condition === undefined) {
|
|
219
|
+
return `(${identFragment2.text} IS NULL)`;
|
|
220
|
+
} else {
|
|
221
|
+
context.values.push(sql.value(condition));
|
|
222
|
+
return `(${identFragment2.text} = ?)`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (typeof condition !== "object" || condition === null) {
|
|
227
|
+
throw new TypeError("Condition must be a value or operator object");
|
|
228
|
+
}
|
|
229
|
+
const operators = condition;
|
|
230
|
+
const clauses = [];
|
|
231
|
+
for (const op of Object.keys(operators)) {
|
|
232
|
+
if (!VALID_OPERATORS.has(op)) {
|
|
233
|
+
throw new SyntaxError(`Unknown operator: ${op}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const identFragment = sql.ident(isJsonPath(field) ? compileJsonPath(field).columnName : field);
|
|
237
|
+
const fieldExpr = isJsonPath(field) ? `json_extract(${identFragment.text}, ?)` : identFragment.text;
|
|
238
|
+
if (isJsonPath(field)) {
|
|
239
|
+
context.values.push(compileJsonPath(field).jsonPath);
|
|
240
|
+
}
|
|
241
|
+
if ("exists" in operators) {
|
|
242
|
+
context.operatorCount++;
|
|
243
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
244
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
245
|
+
}
|
|
246
|
+
return operators.exists ? `(${fieldExpr} IS NOT NULL)` : `(${fieldExpr} IS NULL)`;
|
|
247
|
+
}
|
|
248
|
+
for (const [op, value] of Object.entries(operators)) {
|
|
249
|
+
context.operatorCount++;
|
|
250
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
251
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
252
|
+
}
|
|
253
|
+
switch (op) {
|
|
254
|
+
case "gt":
|
|
255
|
+
context.values.push(sql.value(value));
|
|
256
|
+
clauses.push(`${fieldExpr} > ?`);
|
|
257
|
+
break;
|
|
258
|
+
case "gte":
|
|
259
|
+
context.values.push(sql.value(value));
|
|
260
|
+
clauses.push(`${fieldExpr} >= ?`);
|
|
261
|
+
break;
|
|
262
|
+
case "lt":
|
|
263
|
+
context.values.push(sql.value(value));
|
|
264
|
+
clauses.push(`${fieldExpr} < ?`);
|
|
265
|
+
break;
|
|
266
|
+
case "lte":
|
|
267
|
+
context.values.push(sql.value(value));
|
|
268
|
+
clauses.push(`${fieldExpr} <= ?`);
|
|
269
|
+
break;
|
|
270
|
+
case "ne":
|
|
271
|
+
if (value === null || value === undefined) {
|
|
272
|
+
clauses.push(`${fieldExpr} IS NOT NULL`);
|
|
273
|
+
} else {
|
|
274
|
+
context.values.push(sql.value(value));
|
|
275
|
+
clauses.push(`${fieldExpr} <> ?`);
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
case "in": {
|
|
279
|
+
if (!Array.isArray(value)) {
|
|
280
|
+
throw new TypeError("IN operator requires an array");
|
|
281
|
+
}
|
|
282
|
+
if (value.length === 0) {
|
|
283
|
+
throw new TypeError("IN operator cannot be used with empty arrays");
|
|
284
|
+
}
|
|
285
|
+
if (value.length > 999) {
|
|
286
|
+
throw new RangeError("IN operator cannot be used with arrays larger than 999 items");
|
|
287
|
+
}
|
|
288
|
+
const inFragment = sql.in(value);
|
|
289
|
+
context.values.push(...inFragment.values);
|
|
290
|
+
clauses.push(`${fieldExpr} IN ${inFragment.text}`);
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case "nin": {
|
|
294
|
+
if (!Array.isArray(value)) {
|
|
295
|
+
throw new TypeError("NIN operator requires an array");
|
|
296
|
+
}
|
|
297
|
+
if (value.length === 0) {
|
|
298
|
+
throw new TypeError("NIN operator cannot be used with empty arrays");
|
|
299
|
+
}
|
|
300
|
+
if (value.length > 999) {
|
|
301
|
+
throw new RangeError("NIN operator cannot be used with arrays larger than 999 items");
|
|
302
|
+
}
|
|
303
|
+
const ninFragment = sql.in(value);
|
|
304
|
+
context.values.push(...ninFragment.values);
|
|
305
|
+
clauses.push(`${fieldExpr} NOT IN ${ninFragment.text}`);
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case "like":
|
|
309
|
+
if (typeof value !== "string") {
|
|
310
|
+
throw new TypeError("LIKE operator requires a string pattern");
|
|
311
|
+
}
|
|
312
|
+
context.values.push(value);
|
|
313
|
+
clauses.push(`${fieldExpr} LIKE ?`);
|
|
314
|
+
break;
|
|
315
|
+
case "ilike":
|
|
316
|
+
if (typeof value !== "string") {
|
|
317
|
+
throw new TypeError("ILIKE operator requires a string pattern");
|
|
318
|
+
}
|
|
319
|
+
context.values.push(value);
|
|
320
|
+
clauses.push(`${fieldExpr} LIKE ? COLLATE NOCASE`);
|
|
321
|
+
break;
|
|
322
|
+
case "regex":
|
|
323
|
+
if (typeof value !== "string") {
|
|
324
|
+
throw new TypeError("REGEX operator requires a string pattern");
|
|
325
|
+
}
|
|
326
|
+
context.values.push(value);
|
|
327
|
+
clauses.push(`${fieldExpr} REGEXP ?`);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (clauses.length === 0) {
|
|
332
|
+
throw new SyntaxError("Operator object must contain at least one valid operator");
|
|
333
|
+
}
|
|
334
|
+
return clauses.length === 1 ? `(${clauses[0]})` : `(${clauses.join(" AND ")})`;
|
|
335
|
+
}
|
|
336
|
+
function compileFilterRecursive(filter, context) {
|
|
337
|
+
if (context.depth >= MAX_NESTING_DEPTH) {
|
|
338
|
+
throw new RangeError(`Nesting depth too deep (max: ${MAX_NESTING_DEPTH})`);
|
|
339
|
+
}
|
|
340
|
+
if (typeof filter !== "object" || filter === null) {
|
|
341
|
+
throw new TypeError("Filter must be an object");
|
|
342
|
+
}
|
|
343
|
+
context.depth++;
|
|
344
|
+
const clauses = [];
|
|
345
|
+
if ("and" in filter && filter.and) {
|
|
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.and)) {
|
|
351
|
+
throw new TypeError("AND operator must be an array");
|
|
352
|
+
}
|
|
353
|
+
if (filter.and.length === 0) {
|
|
354
|
+
throw new TypeError("AND operator cannot be used with empty arrays");
|
|
355
|
+
}
|
|
356
|
+
const andClauses = filter.and.map((subFilter) => compileFilterRecursive(subFilter, context));
|
|
357
|
+
clauses.push(`(${andClauses.join(" AND ")})`);
|
|
358
|
+
}
|
|
359
|
+
if ("or" in filter && filter.or) {
|
|
360
|
+
context.operatorCount++;
|
|
361
|
+
if (context.operatorCount > MAX_OPERATORS) {
|
|
362
|
+
throw new RangeError(`Too many operators (max: ${MAX_OPERATORS})`);
|
|
363
|
+
}
|
|
364
|
+
if (!Array.isArray(filter.or)) {
|
|
365
|
+
throw new TypeError("OR operator must be an array");
|
|
366
|
+
}
|
|
367
|
+
if (filter.or.length === 0) {
|
|
368
|
+
throw new TypeError("OR operator cannot be used with empty arrays");
|
|
369
|
+
}
|
|
370
|
+
const orClauses = filter.or.map((subFilter) => compileFilterRecursive(subFilter, context));
|
|
371
|
+
clauses.push(`(${orClauses.join(" OR ")})`);
|
|
372
|
+
}
|
|
373
|
+
const fieldEntries = Object.entries(filter).filter(([field, condition]) => field !== "and" && field !== "or" && condition !== undefined);
|
|
374
|
+
for (const [field, condition] of fieldEntries) {
|
|
375
|
+
if (Array.isArray(condition)) {
|
|
376
|
+
throw new SyntaxError(`Field '${field}' cannot have array value. Use logical operators 'and'/'or' instead.`);
|
|
377
|
+
}
|
|
378
|
+
clauses.push(compileFieldCondition(field, condition, context));
|
|
379
|
+
}
|
|
380
|
+
context.depth--;
|
|
381
|
+
if (clauses.length === 0) {
|
|
382
|
+
throw new SyntaxError("Filter must contain at least one condition");
|
|
383
|
+
}
|
|
384
|
+
if (clauses.length === 1) {
|
|
385
|
+
return clauses[0];
|
|
386
|
+
} else {
|
|
387
|
+
return `(${clauses.join(" AND ")})`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function compileFilter(filter) {
|
|
391
|
+
const context = {
|
|
392
|
+
depth: 0,
|
|
393
|
+
operatorCount: 0,
|
|
394
|
+
values: []
|
|
395
|
+
};
|
|
396
|
+
const text = compileFilterRecursive(filter, context);
|
|
397
|
+
return Object.freeze({
|
|
398
|
+
text: `(${text})`,
|
|
399
|
+
values: Object.freeze([...context.values])
|
|
400
|
+
});
|
|
401
|
+
}
|
|
152
402
|
export {
|
|
153
|
-
sql
|
|
403
|
+
sql,
|
|
404
|
+
compileFilter
|
|
154
405
|
};
|
|
155
406
|
|
|
156
|
-
//# debugId=
|
|
407
|
+
//# debugId=937C5E8A6E98C05964756E2164756E21
|
|
157
408
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/sql.ts"],
|
|
3
|
+
"sources": ["../src/sql.ts", "../src/filter.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
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:
|
|
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// Simple identifier validation (for individual parts)\nconst SIMPLE_IDENTIFIER_REGEX = /^[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 * Quote a single identifier part\n */\nfunction quoteSingleIdentifier(identifier: string): string {\n if (!SIMPLE_IDENTIFIER_REGEX.test(identifier)) {\n throw new TypeError(\n `Invalid identifier part: ${identifier}. Must be a valid ANSI identifier.`,\n )\n }\n return `\"${identifier}\"`\n}\n\n/**\n * Quote a qualified identifier by splitting on dots and quoting each part\n */\nfunction quoteQualifiedIdentifier(identifier: string): string {\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 const parts = identifier.split('.')\n return parts.map(quoteSingleIdentifier).join('.')\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: quoteQualifiedIdentifier(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: quoteQualifiedIdentifier(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"
|
|
6
7
|
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "937C5E8A6E98C05964756E2164756E21",
|
|
9
10
|
"names": []
|
|
10
11
|
}
|
package/dist/sql.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AA6B9D;;GAEG;AACH,iBAAS,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAwB1C;AA4BD;;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
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Safe, zero-dependency template-literal tag for SQLite queries in any JS environment",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sqlite",
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
"database",
|
|
14
14
|
"query-builder"
|
|
15
15
|
],
|
|
16
|
-
"homepage": "https://github.com/
|
|
16
|
+
"homepage": "https://github.com/trutohq/truto-sqlite-builder#readme",
|
|
17
17
|
"bugs": {
|
|
18
|
-
"url": "https://github.com/
|
|
18
|
+
"url": "https://github.com/trutohq/truto-sqlite-builder/issues"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "https://github.com/
|
|
22
|
+
"url": "https://github.com/trutohq/truto-sqlite-builder.git"
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
|
-
"author": "Truto <eng@truto.
|
|
25
|
+
"author": "Truto <eng@truto.one>",
|
|
26
26
|
"type": "module",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|