@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 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
+ [![npm version](https://badge.fury.io/js/%40truto%2Fsqlite-builder.svg)](https://badge.fury.io/js/%40truto%2Fsqlite-builder)
4
+ [![CI](https://github.com/trutohq/truto-sqlite-builder/actions/workflows/ci.yml/badge.svg)](https://github.com/trutohq/truto-sqlite-builder/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/trutohq/truto-sqlite-builder/branch/main/graph/badge.svg)](https://codecov.io/gh/trutohq/truto-sqlite-builder)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * truto-sqlite-builder - Safe, zero-dependency template-literal tag for SQLite queries
3
+ */
4
+ export { sql } from './sql';
5
+ export type { SqlQuery } from './types';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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
+ }