@truto/sqlite-builder 2.0.2-canary.13 → 2.0.2-canary.21
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 +21 -218
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,225 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
// Dynamic column selection (simplified with array support)
|
|
3
|
-
const columns = ['id', 'name', 'email']
|
|
4
|
-
const selectQuery = sql`SELECT ${sql.ident(columns)} FROM users`
|
|
5
|
-
|
|
6
|
-
// Alternative approach for more complex column expressions
|
|
7
|
-
const complexColumns = [
|
|
8
|
-
sql.ident('id'),
|
|
9
|
-
sql.ident('name'),
|
|
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 }] } }
|
|
1
|
+
const results = db.prepare(complexQuery.text).all(...complexQuery.values)
|
|
42
2
|
```
|
|
43
3
|
|
|
44
|
-
|
|
4
|
+
#### Kitchen Sink Examples
|
|
45
5
|
|
|
46
|
-
|
|
6
|
+
Real-world complex filters:
|
|
47
7
|
|
|
48
8
|
```typescript
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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')
|
|
9
|
+
// Active users in specific regions, either minors/seniors or VIP
|
|
10
|
+
const complexFilter = {
|
|
11
|
+
and: [
|
|
12
|
+
{ status: 'ACTIVE' },
|
|
13
|
+
{ or: [{ age: { lt: 18 } }, { age: { gte: 65 } }, { membership: 'VIP' }] },
|
|
14
|
+
{ country: { in: ['US', 'CA', 'GB'] } },
|
|
15
|
+
{ email: { exists: true } },
|
|
16
|
+
{ 'profile.verified': true },
|
|
17
|
+
],
|
|
104
18
|
}
|
|
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
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
const getUsersWithPosts = sql`
|
|
119
|
-
SELECT
|
|
120
|
-
${sql.ident('u')}.id,
|
|
121
|
-
${sql.ident('u')}.name,
|
|
122
|
-
COUNT(${sql.ident('p')}.id) as post_count
|
|
123
|
-
FROM ${sql.ident('users')} u
|
|
124
|
-
LEFT JOIN ${sql.ident('posts')} p ON u.id = p.user_id
|
|
125
|
-
WHERE u.created_at > ${startDate}
|
|
126
|
-
AND u.status IN ${sql.in(['active', 'premium'])}
|
|
127
|
-
GROUP BY u.id
|
|
128
|
-
HAVING post_count > ${minPosts}
|
|
129
|
-
ORDER BY post_count DESC
|
|
130
|
-
LIMIT ${limit}
|
|
131
|
-
`
|
|
132
|
-
|
|
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
19
|
|
|
225
|
-
|
|
20
|
+
// Content filtering with multiple criteria
|
|
21
|
+
const contentFilter = {
|
|
22
|
+
name: { like: 'Project%' },
|
|
23
|
+
category: { nin: ['ARCHIVED', 'DELETED', 'SPAM'] },
|
|
24
|
+
created_at: { exists: true },
|
|
25
|
+
or: [
|
|
26
|
+
{ tags: { ilike: '%important%' } },
|
|
27
|
+
{ priority: { gte: 8 } },
|
|
28
|
+
{ 'metadata.featured': true },
|
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.21","description":"debug canary","license":"MIT","main":"index.js"}
|