@truto/sqlite-builder 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +469 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +296 -0
- package/dist/index.js.map +11 -0
- package/dist/sql.d.ts +40 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +88 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Truto Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# ðïļ truto-sqlite-builder
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/%40truto%2Fsqlite-builder)
|
|
4
|
+
[](https://github.com/trutohq/truto-sqlite-builder/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/trutohq/truto-sqlite-builder)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
**Safe, zero-dependency template-literal tag for SQLite queries in any JS environment.**
|
|
10
|
+
|
|
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.
|
|
12
|
+
|
|
13
|
+
## âĻ Features
|
|
14
|
+
|
|
15
|
+
- ð **Injection-safe**: All values are parameterized, preventing SQL injection
|
|
16
|
+
- ðŦ **Defense in depth**: Multiple security layers including stacked query detection
|
|
17
|
+
- ðŠķ **Zero dependencies**: Pure TypeScript/JavaScript with no runtime dependencies
|
|
18
|
+
- ð **Universal**: Works in Bun, Node.js, Deno, and modern browsers
|
|
19
|
+
- ðŊ **TypeScript-first**: Full type safety with excellent IDE support
|
|
20
|
+
- ð§ **Helper functions**: Built-in utilities for identifiers, IN clauses, and more
|
|
21
|
+
- ⥠**Lightweight**: Minimal bundle size with tree-shaking support
|
|
22
|
+
|
|
23
|
+
## ðĶ Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bun add truto-sqlite-builder
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install truto-sqlite-builder
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
yarn add truto-sqlite-builder
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm add truto-sqlite-builder
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## ð Quick Start
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import sqlite3 from 'better-sqlite3'
|
|
45
|
+
import { sql } from 'truto-sqlite-builder'
|
|
46
|
+
|
|
47
|
+
const db = new sqlite3('database.db')
|
|
48
|
+
|
|
49
|
+
// Simple query
|
|
50
|
+
const name = 'Alice'
|
|
51
|
+
const { text, values } = sql`SELECT * FROM users WHERE name = ${name}`
|
|
52
|
+
const users = db.prepare(text).all(...values)
|
|
53
|
+
|
|
54
|
+
// Complex query with helpers
|
|
55
|
+
const userIds = [1, 2, 3]
|
|
56
|
+
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'}
|
|
62
|
+
`
|
|
63
|
+
|
|
64
|
+
const results = db.prepare(query.text).all(...query.values)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## ð API Reference
|
|
68
|
+
|
|
69
|
+
### `sql` Tagged Template
|
|
70
|
+
|
|
71
|
+
The main function for building SQL queries.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const query = sql`SELECT * FROM users WHERE id = ${userId}`
|
|
75
|
+
// Returns: { text: "SELECT * FROM users WHERE id = ?", values: [userId] }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Parameters:**
|
|
79
|
+
|
|
80
|
+
- Template strings and interpolated values
|
|
81
|
+
- Returns a frozen `SqlQuery` object with `text` and `values` properties
|
|
82
|
+
|
|
83
|
+
### `sql.ident(identifier: string | readonly (string | SqlFragment)[])`
|
|
84
|
+
|
|
85
|
+
Safely quotes SQL identifiers (table names, column names, etc.). Accepts single identifiers, arrays of identifiers, or mixed arrays containing both identifiers and SQL fragments.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Single identifier
|
|
89
|
+
const table = 'users'
|
|
90
|
+
const query = sql`SELECT * FROM ${sql.ident(table)}`
|
|
91
|
+
// Returns: { text: 'SELECT * FROM "users"', values: [] }
|
|
92
|
+
|
|
93
|
+
// Qualified identifiers (table.column)
|
|
94
|
+
const qualifiedQuery = sql`SELECT ${sql.ident('u.name')}, ${sql.ident('u.email')} FROM users u`
|
|
95
|
+
// Returns: { text: 'SELECT "u.name", "u.email" FROM users u', values: [] }
|
|
96
|
+
|
|
97
|
+
// Array of identifiers (useful for column lists)
|
|
98
|
+
const columns = ['name', 'email', 'created_at']
|
|
99
|
+
const selectQuery = sql`SELECT ${sql.ident(columns)} FROM users`
|
|
100
|
+
// Returns: { text: 'SELECT "name", "email", "created_at" FROM users', values: [] }
|
|
101
|
+
|
|
102
|
+
// Mixed arrays with qualified and simple identifiers
|
|
103
|
+
const mixedColumns = ['u.id', 'name', 'u.email', 'p.title']
|
|
104
|
+
const joinQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users u JOIN posts p ON u.id = p.user_id`
|
|
105
|
+
// Returns: { text: 'SELECT "u.id", "name", "u.email", "p.title" FROM users u JOIN posts p ON u.id = p.user_id', values: [] }
|
|
106
|
+
|
|
107
|
+
// Mixed arrays with identifiers and SQL fragments
|
|
108
|
+
const mixedColumns = ['id', 'name', sql.raw('UPPER(email) as email_upper')]
|
|
109
|
+
const mixedQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users`
|
|
110
|
+
// Returns: { text: 'SELECT "id", "name", UPPER(email) as email_upper FROM users', values: [] }
|
|
111
|
+
|
|
112
|
+
// Mixed arrays with parameterized fragments
|
|
113
|
+
const status = 'premium'
|
|
114
|
+
const dynamicColumns = [
|
|
115
|
+
'id',
|
|
116
|
+
'name',
|
|
117
|
+
sql`CASE WHEN status = ${status} THEN 'Premium' ELSE 'Regular' END as user_type`,
|
|
118
|
+
]
|
|
119
|
+
const dynamicQuery = sql`SELECT ${sql.ident(dynamicColumns)} FROM users`
|
|
120
|
+
// Returns: { text: 'SELECT "id", "name", CASE WHEN status = ? THEN \'Premium\' ELSE \'Regular\' END as user_type FROM users', values: ['premium'] }
|
|
121
|
+
|
|
122
|
+
// In INSERT statements
|
|
123
|
+
const insertColumns = ['name', 'email', 'age']
|
|
124
|
+
const insertQuery = sql`
|
|
125
|
+
INSERT INTO users (${sql.ident(insertColumns)})
|
|
126
|
+
VALUES (${name}, ${email}, ${age})
|
|
127
|
+
`
|
|
128
|
+
// Returns: { text: 'INSERT INTO users ("name", "email", "age") VALUES (?, ?, ?)', values: [name, email, age] }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Security:** Only accepts valid ANSI identifiers (simple: `name`, qualified: `table.column`) for string elements. SQL fragments are passed through as-is.
|
|
132
|
+
|
|
133
|
+
### `sql.in(array: readonly unknown[])`
|
|
134
|
+
|
|
135
|
+
Creates parameterized IN clauses from arrays.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const ids = [1, 2, 3]
|
|
139
|
+
const query = sql`SELECT * FROM users WHERE id IN ${sql.in(ids)}`
|
|
140
|
+
// Returns: { text: "SELECT * FROM users WHERE id IN (?,?,?)", values: [1, 2, 3] }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Features:**
|
|
144
|
+
|
|
145
|
+
- Rejects empty arrays (would create invalid SQL)
|
|
146
|
+
- Warns for arrays with >1000 items (performance consideration)
|
|
147
|
+
|
|
148
|
+
### `sql.raw(rawSql: string)`
|
|
149
|
+
|
|
150
|
+
Embeds raw SQL without parameterization. **â ïļ Use with extreme caution!**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const query = sql`SELECT * FROM users WHERE created_at > ${sql.raw('datetime("now", "-1 day")')}`
|
|
154
|
+
// Returns: { text: 'SELECT * FROM users WHERE created_at > datetime("now", "-1 day")', values: [] }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**â ïļ Warning:** Never use `sql.raw()` with user input. Only use with trusted, static SQL fragments.
|
|
158
|
+
|
|
159
|
+
### `sql.join(fragments: SqlFragment[], separator?: string)`
|
|
160
|
+
|
|
161
|
+
Joins multiple SQL fragments with a separator.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const conditions = [
|
|
165
|
+
sql`name = ${'John'}`,
|
|
166
|
+
sql`age = ${30}`,
|
|
167
|
+
sql`active = ${true}`,
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
|
|
171
|
+
// Returns: { text: "SELECT * FROM users WHERE name = ? AND age = ? AND active = ?", values: ['John', 30, true] }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## ðĄïļ Security Model
|
|
175
|
+
|
|
176
|
+
### What's Protected
|
|
177
|
+
|
|
178
|
+
- **SQL Injection**: All interpolated values are parameterized
|
|
179
|
+
- **Stacked Queries**: Queries containing `;` followed by additional SQL are rejected
|
|
180
|
+
- **Identifier Safety**: `sql.ident()` validates against ANSI identifier rules
|
|
181
|
+
- **Length Limits**: Queries exceeding 100KB are rejected (configurable via `TRUTO_SQL_MAX_LENGTH`)
|
|
182
|
+
|
|
183
|
+
### What's Your Responsibility
|
|
184
|
+
|
|
185
|
+
- **Never use `sql.raw()` with user input**
|
|
186
|
+
- **Validate identifiers before using `sql.ident()`** (though it has built-in validation)
|
|
187
|
+
- **Use `sql.in()` instead of string concatenation** for arrays
|
|
188
|
+
- **Keep your SQLite driver updated**
|
|
189
|
+
|
|
190
|
+
### Supported Value Types
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// â
Safe types (automatically parameterized)
|
|
194
|
+
const query = sql`
|
|
195
|
+
INSERT INTO users (name, age, active, created_at, data, deleted_at)
|
|
196
|
+
VALUES (
|
|
197
|
+
${'John'}, // string
|
|
198
|
+
${30}, // number
|
|
199
|
+
${true}, // boolean
|
|
200
|
+
${new Date()}, // Date â 'YYYY-MM-DD HH:MM:SS'
|
|
201
|
+
${null}, // null
|
|
202
|
+
${undefined} // undefined â null
|
|
203
|
+
)
|
|
204
|
+
`
|
|
205
|
+
|
|
206
|
+
// â Unsafe types (will throw TypeError)
|
|
207
|
+
sql`SELECT * FROM users WHERE data = ${Buffer.from('test')}` // Use sql.raw() for buffers
|
|
208
|
+
sql`SELECT * FROM users WHERE id = ${Symbol('test')}` // Unsupported type
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## ð Examples
|
|
212
|
+
|
|
213
|
+
### Basic CRUD Operations
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { sql } from 'truto-sqlite-builder'
|
|
217
|
+
|
|
218
|
+
// CREATE with array identifiers
|
|
219
|
+
const insertColumns = ['name', 'email', 'age']
|
|
220
|
+
const insertUser = sql`
|
|
221
|
+
INSERT INTO users (${sql.ident(insertColumns)})
|
|
222
|
+
VALUES (${name}, ${email}, ${age})
|
|
223
|
+
`
|
|
224
|
+
|
|
225
|
+
// READ with specific columns
|
|
226
|
+
const selectColumns = ['id', 'name', 'email', 'created_at']
|
|
227
|
+
const getUser = sql`
|
|
228
|
+
SELECT ${sql.ident(selectColumns)} FROM users
|
|
229
|
+
WHERE id = ${userId}
|
|
230
|
+
`
|
|
231
|
+
|
|
232
|
+
// UPDATE
|
|
233
|
+
const updateUser = sql`
|
|
234
|
+
UPDATE users
|
|
235
|
+
SET name = ${newName}, updated_at = ${new Date()}
|
|
236
|
+
WHERE id = ${userId}
|
|
237
|
+
`
|
|
238
|
+
|
|
239
|
+
// DELETE
|
|
240
|
+
const deleteUser = sql`
|
|
241
|
+
DELETE FROM users
|
|
242
|
+
WHERE id = ${userId}
|
|
243
|
+
`
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Dynamic Queries
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Dynamic WHERE conditions
|
|
250
|
+
const filters = []
|
|
251
|
+
if (name) filters.push(sql`name = ${name}`)
|
|
252
|
+
if (minAge) filters.push(sql`age >= ${minAge}`)
|
|
253
|
+
if (isActive !== undefined) filters.push(sql`active = ${isActive}`)
|
|
254
|
+
|
|
255
|
+
const whereClause =
|
|
256
|
+
filters.length > 0 ? sql.join(filters, ' AND ') : sql.raw('1=1')
|
|
257
|
+
|
|
258
|
+
const query = sql`
|
|
259
|
+
SELECT * FROM users
|
|
260
|
+
WHERE ${whereClause}
|
|
261
|
+
ORDER BY created_at DESC
|
|
262
|
+
`
|
|
263
|
+
|
|
264
|
+
// Dynamic column selection (simplified with array support)
|
|
265
|
+
const columns = ['id', 'name', 'email']
|
|
266
|
+
const selectQuery = sql`SELECT ${sql.ident(columns)} FROM users`
|
|
267
|
+
|
|
268
|
+
// Alternative approach for more complex column expressions
|
|
269
|
+
const complexColumns = [
|
|
270
|
+
sql.ident('id'),
|
|
271
|
+
sql.ident('name'),
|
|
272
|
+
sql.raw('UPPER(email) as email_upper'),
|
|
273
|
+
]
|
|
274
|
+
const complexQuery = sql`SELECT ${sql.join(complexColumns)} FROM users`
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Array Identifiers & Qualified Identifiers
|
|
278
|
+
|
|
279
|
+
The `sql.ident()` function supports simple identifiers, qualified identifiers (table.column), arrays, and mixed arrays with SQL fragments:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// â
Simple identifiers
|
|
283
|
+
const table = 'users'
|
|
284
|
+
const column = 'name'
|
|
285
|
+
const simpleQuery = sql`SELECT ${sql.ident(column)} FROM ${sql.ident(table)}`
|
|
286
|
+
// Result: SELECT "name" FROM "users"
|
|
287
|
+
|
|
288
|
+
// â
Qualified identifiers (table.column)
|
|
289
|
+
const qualifiedQuery = sql`SELECT ${sql.ident('u.name')}, ${sql.ident('p.title')} FROM users u JOIN posts p ON u.id = p.user_id`
|
|
290
|
+
// Result: SELECT "u.name", "p.title" FROM users u JOIN posts p ON u.id = p.user_id
|
|
291
|
+
|
|
292
|
+
// â
Pure identifier arrays (clean and concise)
|
|
293
|
+
const columns = ['id', 'name', 'email', 'created_at']
|
|
294
|
+
const arrayQuery = sql`SELECT ${sql.ident(columns)} FROM users`
|
|
295
|
+
// Result: SELECT "id", "name", "email", "created_at" FROM users
|
|
296
|
+
|
|
297
|
+
// â
Mixed qualified and simple identifiers in arrays
|
|
298
|
+
const mixedColumns = ['u.id', 'name', 'u.email', 'p.title', 'created_at']
|
|
299
|
+
const mixedQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users u LEFT JOIN posts p ON u.id = p.user_id`
|
|
300
|
+
// Result: SELECT "u.id", "name", "u.email", "p.title", "created_at" FROM users u LEFT JOIN posts p ON u.id = p.user_id
|
|
301
|
+
|
|
302
|
+
// â
NEW: Mixed arrays with identifiers and SQL fragments
|
|
303
|
+
const mixedColumns = [
|
|
304
|
+
'id',
|
|
305
|
+
'name',
|
|
306
|
+
sql.raw('UPPER(email) as email_upper'),
|
|
307
|
+
sql.raw('COUNT(*) as total'),
|
|
308
|
+
]
|
|
309
|
+
const mixedQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users GROUP BY id, name, email`
|
|
310
|
+
// Result: SELECT "id", "name", UPPER(email) as email_upper, COUNT(*) as total FROM users GROUP BY id, name, email
|
|
311
|
+
|
|
312
|
+
// â
Mixed arrays with parameterized fragments
|
|
313
|
+
const status = 'premium'
|
|
314
|
+
const dynamicColumns = [
|
|
315
|
+
'id',
|
|
316
|
+
'name',
|
|
317
|
+
sql`CASE WHEN status = ${status} THEN 'Premium User' ELSE 'Regular User' END as user_type`,
|
|
318
|
+
sql.raw('created_at'),
|
|
319
|
+
]
|
|
320
|
+
const parameterizedQuery = sql`SELECT ${sql.ident(dynamicColumns)} FROM users WHERE active = ${true}`
|
|
321
|
+
// Combines identifiers, raw SQL, and parameterized values seamlessly
|
|
322
|
+
|
|
323
|
+
// â
Works great for INSERT statements
|
|
324
|
+
const insertData = { name: 'John', email: 'john@example.com', age: 30 }
|
|
325
|
+
const insertColumns = Object.keys(insertData)
|
|
326
|
+
const insertValues = Object.values(insertData)
|
|
327
|
+
const insertQuery = sql`
|
|
328
|
+
INSERT INTO users (${sql.ident(insertColumns)})
|
|
329
|
+
VALUES (${insertValues[0]}, ${insertValues[1]}, ${insertValues[2]})
|
|
330
|
+
`
|
|
331
|
+
|
|
332
|
+
// â
Dynamic column selection
|
|
333
|
+
const userFields = ['name', 'email']
|
|
334
|
+
const includeTimestamps = true
|
|
335
|
+
if (includeTimestamps) {
|
|
336
|
+
userFields.push('created_at', 'updated_at')
|
|
337
|
+
}
|
|
338
|
+
const dynamicQuery = sql`SELECT ${sql.ident(userFields)} FROM users`
|
|
339
|
+
|
|
340
|
+
// ð OLD: Manual joining approach (still works, but much more verbose)
|
|
341
|
+
const oldWay = sql`SELECT ${sql.join([
|
|
342
|
+
sql.ident('id'),
|
|
343
|
+
sql.ident('name'),
|
|
344
|
+
sql.raw('UPPER(email) as email_upper'),
|
|
345
|
+
])} FROM users`
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Complex Joins
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
const getUsersWithPosts = sql`
|
|
352
|
+
SELECT
|
|
353
|
+
${sql.ident('u')}.id,
|
|
354
|
+
${sql.ident('u')}.name,
|
|
355
|
+
COUNT(${sql.ident('p')}.id) as post_count
|
|
356
|
+
FROM ${sql.ident('users')} u
|
|
357
|
+
LEFT JOIN ${sql.ident('posts')} p ON u.id = p.user_id
|
|
358
|
+
WHERE u.created_at > ${startDate}
|
|
359
|
+
AND u.status IN ${sql.in(['active', 'premium'])}
|
|
360
|
+
GROUP BY u.id
|
|
361
|
+
HAVING post_count > ${minPosts}
|
|
362
|
+
ORDER BY post_count DESC
|
|
363
|
+
LIMIT ${limit}
|
|
364
|
+
`
|
|
365
|
+
|
|
366
|
+
// For simpler cases, you can use array identifiers directly
|
|
367
|
+
const getUsers = sql`
|
|
368
|
+
SELECT ${sql.ident(['id', 'name', 'email', 'created_at'])}
|
|
369
|
+
FROM ${sql.ident('users')}
|
|
370
|
+
WHERE status = ${'active'}
|
|
371
|
+
`
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Transactions
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// Works great with better-sqlite3 transactions
|
|
378
|
+
const insertUsers = db.transaction((users) => {
|
|
379
|
+
const stmt = db.prepare(
|
|
380
|
+
sql`
|
|
381
|
+
INSERT INTO users (name, email)
|
|
382
|
+
VALUES (?, ?)
|
|
383
|
+
`.text,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
for (const user of users) {
|
|
387
|
+
const { values } = sql`${user.name}, ${user.email}`
|
|
388
|
+
stmt.run(...values)
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
insertUsers([
|
|
393
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
394
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
395
|
+
])
|
|
396
|
+
```
|
|
397
|
+
|
|
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
|
+
## ð§Š Testing
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
# Run tests
|
|
413
|
+
bun run test
|
|
414
|
+
|
|
415
|
+
# Run tests in watch mode
|
|
416
|
+
bun run dev
|
|
417
|
+
|
|
418
|
+
# Run tests with coverage
|
|
419
|
+
bun run test:coverage
|
|
420
|
+
|
|
421
|
+
# Run tests with UI
|
|
422
|
+
bun run test:ui
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## ðĪ Contributing
|
|
426
|
+
|
|
427
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
428
|
+
|
|
429
|
+
### Development Setup
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
git clone https://github.com/truto/truto-sqlite-builder.git
|
|
433
|
+
cd truto-sqlite-builder
|
|
434
|
+
bun install
|
|
435
|
+
bun run dev # Start tests in watch mode
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Release Process
|
|
439
|
+
|
|
440
|
+
We use [changesets](https://github.com/changesets/changesets) for version management:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# Add a changeset
|
|
444
|
+
bunx changeset
|
|
445
|
+
|
|
446
|
+
# Release
|
|
447
|
+
bunx changeset version
|
|
448
|
+
bun run build
|
|
449
|
+
git commit -am "Release"
|
|
450
|
+
git push --follow-tags
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## ð Security Policy
|
|
454
|
+
|
|
455
|
+
If you discover a security vulnerability, please email eng@truto.one or create an issue.
|
|
456
|
+
|
|
457
|
+
## ð License
|
|
458
|
+
|
|
459
|
+
MIT ÂĐ [Truto](https://github.com/trutohq)
|
|
460
|
+
|
|
461
|
+
## ðĄ Inspiration
|
|
462
|
+
|
|
463
|
+
This library was inspired by:
|
|
464
|
+
|
|
465
|
+
- [sql-template-strings](https://github.com/felixfbecker/sql-template-strings)
|
|
466
|
+
- [slonik](https://github.com/gajus/slonik)
|
|
467
|
+
- [Postgres.js](https://github.com/porsager/postgres)
|
|
468
|
+
|
|
469
|
+
Built with âĪïļ for the SQLite community.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
// src/sql.ts
|
|
138
|
+
function getMaxQueryLength() {
|
|
139
|
+
return parseInt(j.env.TRUTO_SQL_MAX_LENGTH || "102400", 10);
|
|
140
|
+
}
|
|
141
|
+
var STACKED_QUERY_REGEX = /;[\s\S]*\S/;
|
|
142
|
+
var QUALIFIED_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
143
|
+
function formatDate(date) {
|
|
144
|
+
const year = date.getFullYear();
|
|
145
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
146
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
147
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
148
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
149
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
150
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
151
|
+
}
|
|
152
|
+
function sqlValue(value) {
|
|
153
|
+
if (value === null || value === undefined) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === "string") {
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
if (value instanceof Date) {
|
|
163
|
+
return formatDate(value);
|
|
164
|
+
}
|
|
165
|
+
if (value instanceof Buffer || value instanceof Uint8Array) {
|
|
166
|
+
throw new TypeError("Buffer/Uint8Array values must be used with sql.blob() for safe BLOB handling");
|
|
167
|
+
}
|
|
168
|
+
throw new TypeError(`Unsupported value type: ${typeof value}`);
|
|
169
|
+
}
|
|
170
|
+
function sqlIdent(identifier) {
|
|
171
|
+
if (Array.isArray(identifier)) {
|
|
172
|
+
if (identifier.length === 0) {
|
|
173
|
+
throw new TypeError("Identifier array cannot be empty");
|
|
174
|
+
}
|
|
175
|
+
const fragments = [];
|
|
176
|
+
for (const item of identifier) {
|
|
177
|
+
if (item && typeof item === "object" && "text" in item && "values" in item && typeof item.text === "string" && Array.isArray(item.values)) {
|
|
178
|
+
fragments.push(item);
|
|
179
|
+
} else if (typeof item === "string") {
|
|
180
|
+
if (!item) {
|
|
181
|
+
throw new TypeError("All identifiers must be non-empty strings");
|
|
182
|
+
}
|
|
183
|
+
if (!QUALIFIED_IDENTIFIER_REGEX.test(item)) {
|
|
184
|
+
throw new TypeError(`Invalid identifier: ${item}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
185
|
+
}
|
|
186
|
+
fragments.push({
|
|
187
|
+
text: '"' + item + '"',
|
|
188
|
+
values: []
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
throw new TypeError("Array items must be strings or SQL fragments");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const text = fragments.map((f2) => f2.text).join(", ");
|
|
195
|
+
const values = fragments.flatMap((f2) => [...f2.values]);
|
|
196
|
+
return {
|
|
197
|
+
text,
|
|
198
|
+
values
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (!identifier || typeof identifier !== "string") {
|
|
202
|
+
throw new TypeError("Identifier must be a non-empty string");
|
|
203
|
+
}
|
|
204
|
+
if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {
|
|
205
|
+
throw new TypeError(`Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
text: '"' + identifier + '"',
|
|
209
|
+
values: []
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function sqlIn(array) {
|
|
213
|
+
if (!Array.isArray(array)) {
|
|
214
|
+
throw new TypeError("sql.in() requires an array");
|
|
215
|
+
}
|
|
216
|
+
if (array.length === 0) {
|
|
217
|
+
throw new TypeError("sql.in() cannot be used with empty arrays");
|
|
218
|
+
}
|
|
219
|
+
if (array.length > 1000) {
|
|
220
|
+
console.warn(`sql.in(): Large array with ${array.length} items. Consider using temporary tables for better performance.`);
|
|
221
|
+
}
|
|
222
|
+
const placeholders = array.map(() => "?").join(",");
|
|
223
|
+
const values = array.map(sqlValue);
|
|
224
|
+
return {
|
|
225
|
+
text: `(${placeholders})`,
|
|
226
|
+
values
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function sqlRaw(rawSql) {
|
|
230
|
+
if (typeof rawSql !== "string") {
|
|
231
|
+
throw new TypeError("sql.raw() requires a string");
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
text: rawSql,
|
|
235
|
+
values: []
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function sqlBlob(data) {
|
|
239
|
+
if (!(data instanceof Buffer) && !(data instanceof Uint8Array)) {
|
|
240
|
+
throw new TypeError("sql.blob() requires a Buffer or Uint8Array");
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
text: "?",
|
|
244
|
+
values: [data]
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function sqlJoin(fragments, separator = ", ") {
|
|
248
|
+
if (!Array.isArray(fragments)) {
|
|
249
|
+
throw new TypeError("sql.join() requires an array of fragments");
|
|
250
|
+
}
|
|
251
|
+
if (fragments.length === 0) {
|
|
252
|
+
return { text: "", values: [] };
|
|
253
|
+
}
|
|
254
|
+
const text = fragments.map((f2) => f2.text).join(separator);
|
|
255
|
+
const values = fragments.flatMap((f2) => [...f2.values]);
|
|
256
|
+
return { text, values };
|
|
257
|
+
}
|
|
258
|
+
function sql(strings, ...values) {
|
|
259
|
+
let text = strings[0] || "";
|
|
260
|
+
const queryValues = [];
|
|
261
|
+
for (let i = 0;i < values.length; i++) {
|
|
262
|
+
const value = values[i];
|
|
263
|
+
if (value && typeof value === "object" && "text" in value && "values" in value && typeof value.text === "string" && Array.isArray(value.values)) {
|
|
264
|
+
const fragment = value;
|
|
265
|
+
text += fragment.text;
|
|
266
|
+
queryValues.push(...fragment.values);
|
|
267
|
+
} else {
|
|
268
|
+
text += "?";
|
|
269
|
+
queryValues.push(sqlValue(value));
|
|
270
|
+
}
|
|
271
|
+
text += strings[i + 1] || "";
|
|
272
|
+
}
|
|
273
|
+
const maxLength = getMaxQueryLength();
|
|
274
|
+
if (text.length > maxLength) {
|
|
275
|
+
throw new Error(`Query too long: ${text.length} bytes (max: ${maxLength})`);
|
|
276
|
+
}
|
|
277
|
+
if (STACKED_QUERY_REGEX.test(text)) {
|
|
278
|
+
throw new Error("Stacked queries are not allowed");
|
|
279
|
+
}
|
|
280
|
+
return Object.freeze({
|
|
281
|
+
text,
|
|
282
|
+
values: Object.freeze([...queryValues])
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
sql.value = sqlValue;
|
|
286
|
+
sql.ident = sqlIdent;
|
|
287
|
+
sql.in = sqlIn;
|
|
288
|
+
sql.raw = sqlRaw;
|
|
289
|
+
sql.blob = sqlBlob;
|
|
290
|
+
sql.join = sqlJoin;
|
|
291
|
+
export {
|
|
292
|
+
sql
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
//# debugId=9115DAB11403208364756E2164756E21
|
|
296
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["node:process", "../src/sql.ts"],
|
|
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"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "9115DAB11403208364756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/dist/sql.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SqlFragment, SqlQuery, SqlValue } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a value to its SQLite representation
|
|
4
|
+
*/
|
|
5
|
+
declare function sqlValue(value: SqlValue): unknown;
|
|
6
|
+
/**
|
|
7
|
+
* Validate and quote a SQL identifier or array of identifiers/fragments
|
|
8
|
+
*/
|
|
9
|
+
declare function sqlIdent(identifier: string | readonly (string | SqlFragment)[]): SqlFragment;
|
|
10
|
+
/**
|
|
11
|
+
* Create SQL IN clause from array
|
|
12
|
+
*/
|
|
13
|
+
declare function sqlIn(array: readonly unknown[]): SqlFragment;
|
|
14
|
+
/**
|
|
15
|
+
* Create raw SQL fragment (DANGEROUS - must not contain user input)
|
|
16
|
+
*/
|
|
17
|
+
declare function sqlRaw(rawSql: string): SqlFragment;
|
|
18
|
+
/**
|
|
19
|
+
* Create SQL fragment for BLOB data (for validated binary data)
|
|
20
|
+
*/
|
|
21
|
+
declare function sqlBlob(data: Buffer | Uint8Array): SqlFragment;
|
|
22
|
+
/**
|
|
23
|
+
* Join SQL fragments with a separator
|
|
24
|
+
*/
|
|
25
|
+
declare function sqlJoin(fragments: readonly SqlFragment[], separator?: string): SqlFragment;
|
|
26
|
+
/**
|
|
27
|
+
* Main SQL tagged template function
|
|
28
|
+
*/
|
|
29
|
+
declare function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlQuery;
|
|
30
|
+
declare namespace sql {
|
|
31
|
+
export var value: typeof sqlValue;
|
|
32
|
+
export var ident: typeof sqlIdent;
|
|
33
|
+
var _a: typeof sqlIn;
|
|
34
|
+
export var raw: typeof sqlRaw;
|
|
35
|
+
export var blob: typeof sqlBlob;
|
|
36
|
+
export var join: typeof sqlJoin;
|
|
37
|
+
export { _a as in };
|
|
38
|
+
}
|
|
39
|
+
export { sql };
|
|
40
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The result of a SQL tagged template
|
|
3
|
+
*/
|
|
4
|
+
export interface SqlQuery {
|
|
5
|
+
readonly text: string;
|
|
6
|
+
readonly values: readonly unknown[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Valid SQL value types that can be safely interpolated
|
|
10
|
+
*/
|
|
11
|
+
export type SqlValue = string | number | boolean | null | undefined | Date | Buffer | Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* A SQL fragment that can be joined with other fragments
|
|
14
|
+
*/
|
|
15
|
+
export interface SqlFragment {
|
|
16
|
+
readonly text: string;
|
|
17
|
+
readonly values: readonly unknown[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@truto/sqlite-builder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Safe, zero-dependency template-literal tag for SQLite queries in any JS environment",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"sqlite",
|
|
7
|
+
"tagged template",
|
|
8
|
+
"sql",
|
|
9
|
+
"injection-safe",
|
|
10
|
+
"javascript",
|
|
11
|
+
"typescript",
|
|
12
|
+
"template-literal",
|
|
13
|
+
"database",
|
|
14
|
+
"query-builder"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/truto/truto-sqlite-builder#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/truto/truto-sqlite-builder/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/truto/truto-sqlite-builder.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Truto <eng@truto.com>",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist/**/*",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "bun build src/index.ts --outdir dist --sourcemap=linked && tsc --emitDeclarationOnly",
|
|
42
|
+
"build:declaration": "tsc --emitDeclarationOnly",
|
|
43
|
+
"dev": "vitest --watch",
|
|
44
|
+
"format": "prettier --write .",
|
|
45
|
+
"format:check": "prettier --check .",
|
|
46
|
+
"lint": "eslint",
|
|
47
|
+
"lint:fix": "eslint --fix",
|
|
48
|
+
"prepublishOnly": "bun run typecheck && bun run lint && bun test && bun run build",
|
|
49
|
+
"test": "vitest",
|
|
50
|
+
"test:coverage": "vitest --coverage",
|
|
51
|
+
"test:ui": "vitest --ui",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
53
|
+
},
|
|
54
|
+
"lint-staged": {
|
|
55
|
+
"*.{ts,tsx}": [
|
|
56
|
+
"bun run eslint --fix"
|
|
57
|
+
],
|
|
58
|
+
"*.{json,md,yml,yaml}": [
|
|
59
|
+
"bun run prettier --write"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@changesets/cli": "2.29.5",
|
|
64
|
+
"@eslint/js": "9.29.0",
|
|
65
|
+
"@types/bun": "latest",
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "8.34.1",
|
|
67
|
+
"@typescript-eslint/parser": "8.34.1",
|
|
68
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
69
|
+
"@vitest/ui": "3.2.4",
|
|
70
|
+
"eslint": "9.29.0",
|
|
71
|
+
"eslint-config-prettier": "10.1.5",
|
|
72
|
+
"eslint-plugin-prettier": "5.5.0",
|
|
73
|
+
"eslint-plugin-security": "3.0.1",
|
|
74
|
+
"globals": "16.2.0",
|
|
75
|
+
"husky": "9.1.7",
|
|
76
|
+
"lint-staged": "16.1.2",
|
|
77
|
+
"prettier": "3.5.3",
|
|
78
|
+
"prettier-plugin-packagejson": "2.5.15",
|
|
79
|
+
"typescript": "5.8.3",
|
|
80
|
+
"vitest": "3.2.4"
|
|
81
|
+
},
|
|
82
|
+
"engines": {
|
|
83
|
+
"bun": ">=1.0.0"
|
|
84
|
+
},
|
|
85
|
+
"publishConfig": {
|
|
86
|
+
"access": "public"
|
|
87
|
+
}
|
|
88
|
+
}
|