@truto/sqlite-builder 2.0.2-canary.0 → 2.0.2-canary.10

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.
Files changed (2) hide show
  1. package/README.md +225 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,225 @@
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
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ **Safe, zero-dependency template-literal tag for SQLite queries in any JS environment.**
9
+
10
+ `@truto/sqlite-builder` provides a secure and ergonomic way to build SQLite queries using tagged template literals. It prevents SQL injection attacks through parameterized queries while offering convenient helper functions for common SQL patterns.
11
+
12
+ ## âœĻ Features
13
+
14
+ - 🔒 **Injection-safe**: All values are parameterized, preventing SQL injection
15
+ - ðŸšŦ **Defense in depth**: Multiple security layers including stacked query detection
16
+ - ðŸŠķ **Zero dependencies**: Pure TypeScript/JavaScript with no runtime dependencies
17
+ - 🌍 **Universal**: Works in Bun, Node.js, Deno, and modern browsers
18
+ - ðŸŽŊ **TypeScript-first**: Full type safety with excellent IDE support
19
+ - 🔧 **Helper functions**: Built-in utilities for identifiers, IN clauses, and more
20
+ - 🔍 **JSON Filter Language**: MongoDB-style JSON filters for WHERE clauses
21
+ - 🔗 **Qualified Filters**: Table/alias scoping for complex JOINs using `$alias` blocks
22
+ - ⚡ **Lightweight**: Minimal bundle size with tree-shaking support
23
+
24
+ ## ðŸ“Ķ Installation
25
+
26
+ ```bash
27
+ bun add @truto/sqlite-builder
28
+ ```
29
+
30
+ ```bash
31
+ npm install @truto/sqlite-builder
32
+ ```
33
+
34
+ ```bash
35
+ yarn add @truto/sqlite-builder
36
+ ```
37
+
38
+ ```bash
39
+ pnpm add @truto/sqlite-builder
40
+ ```
41
+
42
+ ## 🚀 Quick Start
43
+
44
+ ```typescript
45
+ import sqlite3 from 'better-sqlite3'
46
+ import { sql, compileFilter } from '@truto/sqlite-builder'
47
+
48
+ const db = new sqlite3('database.db')
49
+
50
+ // Simple query
51
+ const name = 'Alice'
52
+ const { text, values } = sql`SELECT * FROM users WHERE name = ${name}`
53
+ const users = db.prepare(text).all(...values)
54
+
55
+ // JSON Filter queries
56
+ const filter = {
57
+ name: { like: 'John%' },
58
+ age: { gte: 18, lt: 65 },
59
+ or: [{ email: { regex: '.*@example.com$' } }, { phone: { exists: false } }],
60
+ }
61
+
62
+ // Interpolate the compiled filter directly: its placeholders and values are
63
+ // collected automatically, so query.values is always correctly aligned.
64
+ const query = sql`
65
+ SELECT * FROM users
66
+ WHERE ${compileFilter(filter)}
67
+ `
68
+
69
+ const results = db.prepare(query.text).all(...query.values)
70
+
71
+ // Qualified filters for JOINs with alias blocks
72
+ const joinFilter = {
73
+ status: 'ACTIVE', // Main table
74
+ $profiles: {
75
+ // Profile table alias
76
+ verified: true,
77
+ 'settings.theme': 'dark', // JSON path in profile
78
+ },
79
+ $orders: {
80
+ // Orders table alias
81
+ total: { gt: 100 },
82
+ },
83
+ }
84
+
85
+ const joinQuery = sql`
86
+ SELECT u.name, p.verified, o.total
87
+ FROM users u
88
+ JOIN profiles p ON u.id = p.user_id
89
+ JOIN orders o ON u.id = o.user_id
90
+ WHERE ${compileFilter(joinFilter)}
91
+ `
92
+ ```
93
+
94
+ ## 📖 API Reference
95
+
96
+ ### `sql` Tagged Template
97
+
98
+ The main function for building SQL queries.
99
+
100
+ ```typescript
101
+ const query = sql`SELECT * FROM users WHERE id = ${userId}`
102
+ // Returns: { text: "SELECT * FROM users WHERE id = ?", values: [userId] }
103
+ ```
104
+
105
+ **Parameters:**
106
+
107
+ - Template strings and interpolated values
108
+ - Returns a frozen `SqlQuery` object with `text` and `values` properties
109
+
110
+ ### `sql.ident(identifier: string | readonly (string | SqlFragment)[])`
111
+
112
+ Safely quotes SQL identifiers (table names, column names, etc.). Accepts single identifiers, arrays of identifiers, or mixed arrays containing both identifiers and SQL fragments.
113
+
114
+ ```typescript
115
+ // Single identifier
116
+ const table = 'users'
117
+ const query = sql`SELECT * FROM ${sql.ident(table)}`
118
+ // Returns: { text: 'SELECT * FROM "users"', values: [] }
119
+
120
+ // Qualified identifiers (table.column)
121
+ const qualifiedQuery = sql`SELECT ${sql.ident('u.name')}, ${sql.ident('u.email')} FROM users u`
122
+ // Returns: { text: 'SELECT "u"."name", "u"."email" FROM users u', values: [] }
123
+
124
+ // Array of identifiers (useful for column lists)
125
+ const columns = ['name', 'email', 'created_at']
126
+ const selectQuery = sql`SELECT ${sql.ident(columns)} FROM users`
127
+ // Returns: { text: 'SELECT "name", "email", "created_at" FROM users', values: [] }
128
+
129
+ // Mixed arrays with qualified and simple identifiers
130
+ const mixedColumns = ['u.id', 'name', 'u.email', 'p.title']
131
+ const joinQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users u JOIN posts p ON u.id = p.user_id`
132
+ // Returns: { text: 'SELECT "u"."id", "name", "u"."email", "p"."title" FROM users u JOIN posts p ON u.id = p.user_id', values: [] }
133
+
134
+ // Mixed arrays with identifiers and SQL fragments
135
+ const mixedColumns = ['id', 'name', sql.raw('UPPER(email) as email_upper')]
136
+ const mixedQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users`
137
+ // Returns: { text: 'SELECT "id", "name", UPPER(email) as email_upper FROM users', values: [] }
138
+
139
+ // Mixed arrays with parameterized fragments
140
+ const status = 'premium'
141
+ const dynamicColumns = [
142
+ 'id',
143
+ 'name',
144
+ sql`CASE WHEN status = ${status} THEN 'Premium' ELSE 'Regular' END as user_type`,
145
+ ]
146
+ const dynamicQuery = sql`SELECT ${sql.ident(dynamicColumns)} FROM users`
147
+ // Returns: { text: 'SELECT "id", "name", CASE WHEN status = ? THEN \'Premium\' ELSE \'Regular\' END as user_type FROM users', values: ['premium'] }
148
+
149
+ // In INSERT statements
150
+ const insertColumns = ['name', 'email', 'age']
151
+ const insertQuery = sql`
152
+ INSERT INTO users (${sql.ident(insertColumns)})
153
+ VALUES (${name}, ${email}, ${age})
154
+ `
155
+ // Returns: { text: 'INSERT INTO users ("name", "email", "age") VALUES (?, ?, ?)', values: [name, email, age] }
156
+ ```
157
+
158
+ **Security:** Only accepts valid ANSI identifiers (simple: `name`, qualified: `table.column`) for string elements, each capped at 255 characters. SQL fragments are passed through as-is, but **only fragments minted by this library** (e.g. `sql.raw`, nested `sql\`\``) are accepted — a plain `{ text, values }`object (for example from`JSON.parse`) is rejected, so untrusted data can never masquerade as raw SQL.
159
+
160
+ ### `sql.in(array: readonly unknown[])`
161
+
162
+ Creates parameterized IN clauses from arrays.
163
+
164
+ ```typescript
165
+ const ids = [1, 2, 3]
166
+ const query = sql`SELECT * FROM users WHERE id IN ${sql.in(ids)}`
167
+ // Returns: { text: "SELECT * FROM users WHERE id IN (?,?,?)", values: [1, 2, 3] }
168
+ ```
169
+
170
+ **Features:**
171
+
172
+ - Rejects empty arrays (would create invalid SQL)
173
+ - Warns for arrays with >1000 items (performance consideration)
174
+
175
+ ### `sql.raw(rawSql: string)`
176
+
177
+ Embeds raw SQL without parameterization. **⚠ïļ Use with extreme caution!**
178
+
179
+ ```typescript
180
+ const query = sql`SELECT * FROM users WHERE created_at > ${sql.raw('datetime("now", "-1 day")')}`
181
+ // Returns: { text: 'SELECT * FROM users WHERE created_at > datetime("now", "-1 day")', values: [] }
182
+ ```
183
+
184
+ **⚠ïļ Warning:** `sql.raw()` is the library's single, explicit trust boundary — its argument becomes SQL verbatim. Never pass user input to it. For dynamic WHERE clauses use `compileFilter()`; for identifiers use `sql.ident()`. As a safety net, the `sql` tag verifies that the number of `?` placeholders in the final query exactly matches the number of bound values, so a raw fragment that smuggles a stray `?` (or forgets to carry its value) throws instead of producing a misaligned query.
185
+
186
+ ### `sql.join(fragments: SqlFragment[], separator?: string | SqlFragment)`
187
+
188
+ Joins multiple SQL fragments with a separator.
189
+
190
+ ```typescript
191
+ const conditions = [
192
+ sql`name = ${'John'}`,
193
+ sql`age = ${30}`,
194
+ sql`active = ${true}`,
195
+ ]
196
+
197
+ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
198
+ // Returns: { text: "SELECT * FROM users WHERE name = ? AND age = ? AND active = ?", values: ['John', 30, true] }
199
+ ```
200
+
201
+ **Fragments** must be library-minted fragments (forged `{ text, values }` objects are rejected).
202
+
203
+ **Separators** support any structural connector safely:
204
+
205
+ - A **string** separator (e.g. `', '`, `' AND '`, `' UNION ALL '`) is validated to be a pure connector. Separators containing string/identifier quotes (`'`, `"`, `` ` ``, `[`, `]`), statement terminators (`;`), comment markers (`--`, `/*`, `*/`), `NUL`, backslashes, or unbalanced parentheses are rejected — so even an untrusted separator cannot break out of the expression.
206
+ - A **`SqlFragment`** separator (e.g. `sql\` OR weight = ${w} OR \``) lets you parameterize the connector itself; its values are interleaved between fragments automatically.
207
+
208
+ ## 🔍 JSON Filter Language
209
+
210
+ Build complex WHERE clauses using MongoDB-style JSON filters. Perfect for APIs and dynamic queries.
211
+
212
+ ### `compileFilter(filter: JsonFilter): FilterResult`
213
+
214
+ Compiles a JSON filter object into a parameterized SQL WHERE clause.
215
+
216
+ ```typescript
217
+ import { compileFilter } from '@truto/sqlite-builder'
218
+
219
+ const filter = {
220
+ status: 'ACTIVE',
221
+ age: { gte: 18, lt: 65 },
222
+ }
223
+
224
+ const result = compileFilter(filter)
225
+ // Returns a branded SQL fragment: { text: '(("status" = ?) AND ("age" >= ?) AND ("age" < ?))', values: ['ACTIVE', 18, 65] }
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@truto/sqlite-builder","version":"2.0.2-canary.0","description":"debug canary","license":"MIT","main":"index.js"}
1
+ {"name":"@truto/sqlite-builder","version":"2.0.2-canary.10","description":"debug canary","license":"MIT","main":"index.js"}