@truto/sqlite-builder 2.0.2-canary.13 → 2.0.2-canary.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -220
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,225 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
sql.raw('UPPER(email) as email_upper'),
|
|
11
|
-
]
|
|
12
|
-
const complexQuery = sql`SELECT ${sql.join(complexColumns)} FROM users`
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### Dynamic Queries with JSON Filters
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { sql, compileFilter } from '@truto/sqlite-builder'
|
|
19
|
-
|
|
20
|
-
// API endpoint that accepts JSON filter
|
|
21
|
-
app.get('/api/users', (req, res) => {
|
|
22
|
-
// User sends filter as JSON
|
|
23
|
-
const filter = req.body.filter || {}
|
|
24
|
-
|
|
25
|
-
// Safely compile to SQL and interpolate directly. Filter values and the
|
|
26
|
-
// LIMIT value are collected in order into query.values.
|
|
27
|
-
const query = sql`
|
|
28
|
-
SELECT id, name, email, created_at
|
|
29
|
-
FROM users
|
|
30
|
-
WHERE ${compileFilter(filter)}
|
|
31
|
-
ORDER BY created_at DESC
|
|
32
|
-
LIMIT ${req.query.limit || 20}
|
|
33
|
-
`
|
|
34
|
-
|
|
35
|
-
const users = db.prepare(query.text).all(...query.values)
|
|
36
|
-
res.json(users)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// Example API calls:
|
|
40
|
-
// POST /api/users { "filter": { "status": "ACTIVE", "age": { "gte": 18 } } }
|
|
41
|
-
// POST /api/users { "filter": { "or": [{ "role": "ADMIN" }, { "verified": true }] } }
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Array Identifiers & Qualified Identifiers
|
|
45
|
-
|
|
46
|
-
The `sql.ident()` function supports simple identifiers, qualified identifiers (table.column), arrays, and mixed arrays with SQL fragments:
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
// ✅ Simple identifiers
|
|
50
|
-
const table = 'users'
|
|
51
|
-
const column = 'name'
|
|
52
|
-
const simpleQuery = sql`SELECT ${sql.ident(column)} FROM ${sql.ident(table)}`
|
|
53
|
-
// Result: SELECT "name" FROM "users"
|
|
54
|
-
|
|
55
|
-
// ✅ Qualified identifiers (table.column)
|
|
56
|
-
const qualifiedQuery = sql`SELECT ${sql.ident('u.name')}, ${sql.ident('p.title')} FROM users u JOIN posts p ON u.id = p.user_id`
|
|
57
|
-
// Result: SELECT "u"."name", "p"."title" FROM users u JOIN posts p ON u.id = p.user_id
|
|
58
|
-
|
|
59
|
-
// ✅ Pure identifier arrays (clean and concise)
|
|
60
|
-
const columns = ['id', 'name', 'email', 'created_at']
|
|
61
|
-
const arrayQuery = sql`SELECT ${sql.ident(columns)} FROM users`
|
|
62
|
-
// Result: SELECT "id", "name", "email", "created_at" FROM users
|
|
63
|
-
|
|
64
|
-
// ✅ Mixed qualified and simple identifiers in arrays
|
|
65
|
-
const mixedColumns = ['u.id', 'name', 'u.email', 'p.title', 'created_at']
|
|
66
|
-
const mixedQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users u LEFT JOIN posts p ON u.id = p.user_id`
|
|
67
|
-
// Result: SELECT "u"."id", "name", "u"."email", "p"."title", "created_at" FROM users u LEFT JOIN posts p ON u.id = p.user_id
|
|
68
|
-
|
|
69
|
-
// ✅ NEW: Mixed arrays with identifiers and SQL fragments
|
|
70
|
-
const mixedColumns = [
|
|
71
|
-
'id',
|
|
72
|
-
'name',
|
|
73
|
-
sql.raw('UPPER(email) as email_upper'),
|
|
74
|
-
sql.raw('COUNT(*) as total'),
|
|
75
|
-
]
|
|
76
|
-
const mixedQuery = sql`SELECT ${sql.ident(mixedColumns)} FROM users GROUP BY id, name, email`
|
|
77
|
-
// Result: SELECT "id", "name", UPPER(email) as email_upper, COUNT(*) as total FROM users GROUP BY id, name, email
|
|
78
|
-
|
|
79
|
-
// ✅ Mixed arrays with parameterized fragments
|
|
80
|
-
const status = 'premium'
|
|
81
|
-
const dynamicColumns = [
|
|
82
|
-
'id',
|
|
83
|
-
'name',
|
|
84
|
-
sql`CASE WHEN status = ${status} THEN 'Premium User' ELSE 'Regular User' END as user_type`,
|
|
85
|
-
sql.raw('created_at'),
|
|
86
|
-
]
|
|
87
|
-
const parameterizedQuery = sql`SELECT ${sql.ident(dynamicColumns)} FROM users WHERE active = ${true}`
|
|
88
|
-
// Combines identifiers, raw SQL, and parameterized values seamlessly
|
|
89
|
-
|
|
90
|
-
// ✅ Works great for INSERT statements
|
|
91
|
-
const insertData = { name: 'John', email: 'john@example.com', age: 30 }
|
|
92
|
-
const insertColumns = Object.keys(insertData)
|
|
93
|
-
const insertValues = Object.values(insertData)
|
|
94
|
-
const insertQuery = sql`
|
|
95
|
-
INSERT INTO users (${sql.ident(insertColumns)})
|
|
96
|
-
VALUES (${insertValues[0]}, ${insertValues[1]}, ${insertValues[2]})
|
|
97
|
-
`
|
|
98
|
-
|
|
99
|
-
// ✅ Dynamic column selection
|
|
100
|
-
const userFields = ['name', 'email']
|
|
101
|
-
const includeTimestamps = true
|
|
102
|
-
if (includeTimestamps) {
|
|
103
|
-
userFields.push('created_at', 'updated_at')
|
|
1
|
+
verified_at: { exists: true },
|
|
2
|
+
},
|
|
3
|
+
|
|
4
|
+
// Recent orders
|
|
5
|
+
$recent_orders: {
|
|
6
|
+
created_at: { gte: '2024-01-01' },
|
|
7
|
+
status: { in: ['completed', 'shipped'] },
|
|
8
|
+
total: { gt: 50 },
|
|
9
|
+
},
|
|
104
10
|
}
|
|
105
|
-
const dynamicQuery = sql`SELECT ${sql.ident(userFields)} FROM users`
|
|
106
|
-
|
|
107
|
-
// 🆚 OLD: Manual joining approach (still works, but much more verbose)
|
|
108
|
-
const oldWay = sql`SELECT ${sql.join([
|
|
109
|
-
sql.ident('id'),
|
|
110
|
-
sql.ident('name'),
|
|
111
|
-
sql.raw('UPPER(email) as email_upper'),
|
|
112
|
-
])} FROM users`
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Complex Joins
|
|
116
11
|
|
|
117
|
-
|
|
118
|
-
const getUsersWithPosts = sql`
|
|
12
|
+
const complexQuery = sql`
|
|
119
13
|
SELECT
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
14
|
+
u.id,
|
|
15
|
+
u.name,
|
|
16
|
+
u.email,
|
|
17
|
+
p.verified_at,
|
|
18
|
+
COUNT(ro.id) as recent_order_count,
|
|
19
|
+
SUM(ro.total) as recent_order_total
|
|
20
|
+
FROM users u
|
|
21
|
+
JOIN profiles p ON u.id = p.user_id
|
|
22
|
+
JOIN orders ro ON u.id = ro.user_id
|
|
23
|
+
WHERE ${compileFilter(userOrderFilter)}
|
|
24
|
+
GROUP BY u.id, u.name, u.email, p.verified_at
|
|
25
|
+
HAVING recent_order_count > 0
|
|
26
|
+
ORDER BY recent_order_total DESC
|
|
131
27
|
`
|
|
132
28
|
|
|
133
|
-
// For simpler cases, you can use array identifiers directly
|
|
134
|
-
const getUsers = sql`
|
|
135
|
-
SELECT ${sql.ident(['id', 'name', 'email', 'created_at'])}
|
|
136
|
-
FROM ${sql.ident('users')}
|
|
137
|
-
WHERE status = ${'active'}
|
|
138
|
-
`
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Transactions
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
// Works great with better-sqlite3 transactions
|
|
145
|
-
const insertUsers = db.transaction((users) => {
|
|
146
|
-
const stmt = db.prepare(
|
|
147
|
-
sql`
|
|
148
|
-
INSERT INTO users (name, email)
|
|
149
|
-
VALUES (?, ?)
|
|
150
|
-
`.text,
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
for (const user of users) {
|
|
154
|
-
const { values } = sql`${user.name}, ${user.email}`
|
|
155
|
-
stmt.run(...values)
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
insertUsers([
|
|
160
|
-
{ name: 'Alice', email: 'alice@example.com' },
|
|
161
|
-
{ name: 'Bob', email: 'bob@example.com' },
|
|
162
|
-
])
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## 🧪 Testing
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
# Run tests
|
|
169
|
-
bun run test
|
|
170
|
-
|
|
171
|
-
# Run tests in watch mode
|
|
172
|
-
bun run dev
|
|
173
|
-
|
|
174
|
-
# Run tests with coverage
|
|
175
|
-
bun run test:coverage
|
|
176
|
-
|
|
177
|
-
# Run tests with UI
|
|
178
|
-
bun run test:ui
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## 🤝 Contributing
|
|
182
|
-
|
|
183
|
-
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
184
|
-
|
|
185
|
-
### Development Setup
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
git clone https://github.com/truto/truto-sqlite-builder.git
|
|
189
|
-
cd truto-sqlite-builder
|
|
190
|
-
bun install
|
|
191
|
-
bun run dev # Start tests in watch mode
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Release Process
|
|
195
|
-
|
|
196
|
-
We use [changesets](https://github.com/changesets/changesets) for version management:
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
# Add a changeset
|
|
200
|
-
bunx changeset
|
|
201
|
-
|
|
202
|
-
# Release
|
|
203
|
-
bunx changeset version
|
|
204
|
-
bun run build
|
|
205
|
-
git commit -am "Release"
|
|
206
|
-
git push --follow-tags
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## 🔒 Security Policy
|
|
210
|
-
|
|
211
|
-
If you discover a security vulnerability, please email eng@truto.one or create an issue.
|
|
212
|
-
|
|
213
|
-
## 📄 License
|
|
214
|
-
|
|
215
|
-
MIT © [Truto](https://github.com/trutohq)
|
|
216
|
-
|
|
217
|
-
## 💡 Inspiration
|
|
218
|
-
|
|
219
|
-
This library was inspired by:
|
|
220
|
-
|
|
221
|
-
- [sql-template-strings](https://github.com/felixfbecker/sql-template-strings)
|
|
222
|
-
- [slonik](https://github.com/gajus/slonik)
|
|
223
|
-
- [Postgres.js](https://github.com/porsager/postgres)
|
|
224
|
-
|
|
225
|
-
Built with ❤️ for the SQLite community.
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@truto/sqlite-builder","version":"2.0.2-canary.
|
|
1
|
+
{"name":"@truto/sqlite-builder","version":"2.0.2-canary.20","description":"debug canary","license":"MIT","main":"index.js"}
|