@pineliner/odb-client 1.0.6 → 1.0.7
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/dist/core/http-client.d.ts.map +1 -1
- package/dist/database/adapters/bun-sqlite.d.ts.map +1 -1
- package/dist/database/adapters/libsql.d.ts.map +1 -1
- package/dist/database/adapters/odblite.d.ts +2 -1
- package/dist/database/adapters/odblite.d.ts.map +1 -1
- package/dist/database/index.d.ts +2 -0
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/sql-template.d.ts +432 -0
- package/dist/database/sql-template.d.ts.map +1 -0
- package/dist/database/sql-template.examples.d.ts +28 -0
- package/dist/database/sql-template.examples.d.ts.map +1 -0
- package/dist/database/types.d.ts +8 -1
- package/dist/database/types.d.ts.map +1 -1
- package/dist/index.cjs +1861 -1669
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +779 -653
- package/dist/orm/index.d.ts +228 -0
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/http-client.ts +1 -0
- package/src/database/adapters/bun-sqlite.ts +73 -15
- package/src/database/adapters/libsql.ts +73 -15
- package/src/database/adapters/odblite.ts +87 -23
- package/src/database/index.ts +4 -0
- package/src/database/sql-template.examples.ts +363 -0
- package/src/database/sql-template.ts +660 -0
- package/src/database/types.ts +15 -3
- package/src/index.ts +31 -0
- package/src/orm/index.ts +538 -0
- package/src/types.ts +2 -0
|
@@ -7,6 +7,16 @@ import type {
|
|
|
7
7
|
PreparedStatement,
|
|
8
8
|
ODBLiteConfig,
|
|
9
9
|
} from '../types'
|
|
10
|
+
import {
|
|
11
|
+
convertTemplateToQuery,
|
|
12
|
+
sql as sqlHelper,
|
|
13
|
+
empty,
|
|
14
|
+
raw,
|
|
15
|
+
fragment,
|
|
16
|
+
join,
|
|
17
|
+
set,
|
|
18
|
+
where
|
|
19
|
+
} from '../sql-template'
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
22
|
* ODB-Lite adapter for DatabaseManager
|
|
@@ -15,7 +25,7 @@ import type {
|
|
|
15
25
|
export class ODBLiteAdapter implements DatabaseAdapter {
|
|
16
26
|
readonly type = 'odblite' as const
|
|
17
27
|
private config: ODBLiteConfig
|
|
18
|
-
|
|
28
|
+
public serviceClient: ServiceClient
|
|
19
29
|
|
|
20
30
|
constructor(config: ODBLiteConfig) {
|
|
21
31
|
this.config = config
|
|
@@ -27,19 +37,24 @@ export class ODBLiteAdapter implements DatabaseAdapter {
|
|
|
27
37
|
|
|
28
38
|
async connect(config: any): Promise<Connection> {
|
|
29
39
|
const databaseName = config.databaseName || 'default'
|
|
40
|
+
const databaseHash = config.databaseHash // Optional: connect directly by hash
|
|
30
41
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
// If hash is provided, skip database lookup and connect directly
|
|
43
|
+
if (databaseHash) {
|
|
44
|
+
const client = new ODBLiteClient({
|
|
45
|
+
baseUrl: this.config.serviceUrl,
|
|
46
|
+
apiKey: this.config.apiKey,
|
|
47
|
+
databaseId: databaseHash,
|
|
48
|
+
})
|
|
49
|
+
return new ODBLiteConnection(client, this.serviceClient, databaseName, databaseHash)
|
|
37
50
|
}
|
|
38
51
|
|
|
39
|
-
//
|
|
52
|
+
// Otherwise, query by name
|
|
53
|
+
// Check if database exists
|
|
40
54
|
const db = await this.serviceClient.getDatabaseByName(databaseName)
|
|
55
|
+
|
|
41
56
|
if (!db) {
|
|
42
|
-
throw new Error(`Database ${databaseName} not found
|
|
57
|
+
throw new Error(`Database ${databaseName} not found. Please create it first using the admin API.`)
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
// Create ODBLiteClient for this database
|
|
@@ -87,6 +102,9 @@ class ODBLiteConnection implements Connection {
|
|
|
87
102
|
public databaseName: string
|
|
88
103
|
public databaseHash: string
|
|
89
104
|
|
|
105
|
+
// Dual-purpose sql method (postgres.js-compatible)
|
|
106
|
+
public sql: any
|
|
107
|
+
|
|
90
108
|
constructor(
|
|
91
109
|
client: ODBLiteClient,
|
|
92
110
|
serviceClient: ServiceClient,
|
|
@@ -97,22 +115,48 @@ class ODBLiteConnection implements Connection {
|
|
|
97
115
|
this.serviceClient = serviceClient
|
|
98
116
|
this.databaseName = databaseName
|
|
99
117
|
this.databaseHash = databaseHash
|
|
100
|
-
}
|
|
101
118
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
119
|
+
// Create dual-purpose sql method (postgres.js lazy evaluation pattern)
|
|
120
|
+
// Handles both template tag calls AND helper function calls
|
|
121
|
+
const self = this
|
|
122
|
+
this.sql = function(stringsOrValue: any, ...values: any[]): any {
|
|
123
|
+
// Check if called as template tag (has 'raw' property)
|
|
124
|
+
if (stringsOrValue && typeof stringsOrValue === 'object' && 'raw' in stringsOrValue) {
|
|
125
|
+
// Template literal call: sql`SELECT...`
|
|
126
|
+
// Return a thenable SqlFragment (lazy evaluation):
|
|
127
|
+
// - Can be embedded in other queries as SqlFragment
|
|
128
|
+
// - Can be awaited to execute and return rows
|
|
129
|
+
const query = convertTemplateToQuery(stringsOrValue, values)
|
|
111
130
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
// Create thenable fragment
|
|
132
|
+
const thenableFragment: any = {
|
|
133
|
+
_isSqlFragment: true,
|
|
134
|
+
sql: query.sql,
|
|
135
|
+
args: query.args
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Make it awaitable by adding then method (using bracket notation to avoid linter error)
|
|
139
|
+
thenableFragment['then'] = function(onFulfilled: any, onRejected: any) {
|
|
140
|
+
return self.execute(query)
|
|
141
|
+
.then(result => result.rows)
|
|
142
|
+
.then(onFulfilled, onRejected)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return thenableFragment
|
|
146
|
+
} else {
|
|
147
|
+
// Regular function call: sql(['col1', 'col2']) or sql(obj, 'key')
|
|
148
|
+
// Return SqlFragment for use in template literals
|
|
149
|
+
return sqlHelper(stringsOrValue, ...values)
|
|
150
|
+
}
|
|
115
151
|
}
|
|
152
|
+
|
|
153
|
+
// Attach helper methods as properties (postgres.js style)
|
|
154
|
+
this.sql.empty = empty
|
|
155
|
+
this.sql.raw = raw
|
|
156
|
+
this.sql.fragment = fragment
|
|
157
|
+
this.sql.join = join
|
|
158
|
+
this.sql.set = set
|
|
159
|
+
this.sql.where = where
|
|
116
160
|
}
|
|
117
161
|
|
|
118
162
|
/**
|
|
@@ -129,22 +173,34 @@ class ODBLiteConnection implements Connection {
|
|
|
129
173
|
try {
|
|
130
174
|
// Handle object format { sql, args }
|
|
131
175
|
if (typeof sql === 'object') {
|
|
176
|
+
// Debug logging for SQL errors
|
|
177
|
+
if (process.env.DEBUG_SQL) {
|
|
178
|
+
console.log('[ODBLite] Executing SQL:', sql.sql)
|
|
179
|
+
console.log('[ODBLite] With args:', sql.args)
|
|
180
|
+
}
|
|
132
181
|
const result = await this.client.sql.execute(sql.sql, sql.args || [])
|
|
133
182
|
return {
|
|
134
183
|
rows: result.rows,
|
|
135
184
|
rowsAffected: result.rowsAffected || 0,
|
|
185
|
+
lastInsertRowid: (result as any).lastInsertRowid,
|
|
136
186
|
}
|
|
137
187
|
}
|
|
138
188
|
|
|
139
189
|
// Handle string format
|
|
190
|
+
if (process.env.DEBUG_SQL) {
|
|
191
|
+
console.log('[ODBLite] Executing SQL:', sql)
|
|
192
|
+
console.log('[ODBLite] With params:', params)
|
|
193
|
+
}
|
|
140
194
|
const result = await this.client.sql.execute(sql, params)
|
|
141
195
|
|
|
142
196
|
return {
|
|
143
197
|
rows: result.rows,
|
|
144
198
|
rowsAffected: result.rowsAffected || 0,
|
|
199
|
+
lastInsertRowid: (result as any).lastInsertRowid,
|
|
145
200
|
}
|
|
146
201
|
} catch (error: any) {
|
|
147
|
-
throw
|
|
202
|
+
// Re-throw the original error to let the application handle it
|
|
203
|
+
throw error
|
|
148
204
|
}
|
|
149
205
|
}
|
|
150
206
|
|
|
@@ -207,4 +263,12 @@ class ODBLiteConnection implements Connection {
|
|
|
207
263
|
// ODBLiteClient connections are stateless HTTP requests
|
|
208
264
|
// No explicit close needed
|
|
209
265
|
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Create ORM instance for this connection
|
|
269
|
+
*/
|
|
270
|
+
createORM(): any {
|
|
271
|
+
const { createORM } = require('../../orm/index.ts')
|
|
272
|
+
return createORM(this)
|
|
273
|
+
}
|
|
210
274
|
}
|
package/src/database/index.ts
CHANGED
|
@@ -5,6 +5,10 @@ export { DatabaseManager } from './manager'
|
|
|
5
5
|
export { parseSQL, splitSQLStatements } from './sql-parser'
|
|
6
6
|
export type { ParsedStatements, SQLParserOptions } from './sql-parser'
|
|
7
7
|
|
|
8
|
+
// SQL Template helpers (postgres.js-compatible)
|
|
9
|
+
export { sql, raw, empty, fragment, join, set, where, convertTemplateToQuery } from './sql-template'
|
|
10
|
+
export type { SqlQuery, SqlFragment } from './sql-template'
|
|
11
|
+
|
|
8
12
|
// Backend adapters
|
|
9
13
|
export { BunSQLiteAdapter } from './adapters/bun-sqlite'
|
|
10
14
|
export { LibSQLAdapter } from './adapters/libsql'
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL Template Examples - postgres.js Compatible Usage
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates all supported postgres.js patterns with our sql-template implementation.
|
|
5
|
+
* These examples show the exact usage from postgres.js documentation.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This file is for documentation only - not included in build
|
|
8
|
+
* @file sql-template.examples.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// @ts-nocheck - This file is for documentation purposes only
|
|
12
|
+
|
|
13
|
+
import { sql, set, fragment, empty, raw, join, where } from './sql-template'
|
|
14
|
+
import type { Connection } from './types'
|
|
15
|
+
|
|
16
|
+
// Mock connection for type checking
|
|
17
|
+
declare const db: any
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Example 1: Query Parameters (Basic)
|
|
21
|
+
*/
|
|
22
|
+
async function example1_queryParameters() {
|
|
23
|
+
const name = 'John'
|
|
24
|
+
const age = 30
|
|
25
|
+
|
|
26
|
+
// Simple parameterized query
|
|
27
|
+
await db`SELECT * FROM users WHERE name = ${name} AND age > ${age}`
|
|
28
|
+
// → SELECT * FROM users WHERE name = ? AND age = ?
|
|
29
|
+
// → args: ["John", 30]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Example 2: Dynamic Column Selection
|
|
34
|
+
*/
|
|
35
|
+
async function example2_dynamicColumns() {
|
|
36
|
+
const columns = ['name', 'age']
|
|
37
|
+
|
|
38
|
+
await db`
|
|
39
|
+
SELECT ${sql(columns)}
|
|
40
|
+
FROM users
|
|
41
|
+
`
|
|
42
|
+
// → SELECT name, age FROM users
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Example 3: Dynamic Insert (Single Object)
|
|
47
|
+
*/
|
|
48
|
+
async function example3_dynamicInsert() {
|
|
49
|
+
const user = {
|
|
50
|
+
name: 'Murray',
|
|
51
|
+
age: 68
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// With explicit columns
|
|
55
|
+
await db`INSERT INTO users ${sql(user, 'name', 'age')}`
|
|
56
|
+
// → INSERT INTO users (name, age) VALUES (?, ?)
|
|
57
|
+
// → args: ["Murray", 68]
|
|
58
|
+
|
|
59
|
+
// With columns array
|
|
60
|
+
const columns = ['name', 'age']
|
|
61
|
+
await db`INSERT INTO users ${sql(user, columns)}`
|
|
62
|
+
// → INSERT INTO users (name, age) VALUES (?, ?)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Example 4: Bulk Insert (Array of Objects)
|
|
67
|
+
*/
|
|
68
|
+
async function example4_bulkInsert() {
|
|
69
|
+
const users = [
|
|
70
|
+
{ name: 'Murray', age: 68, garbage: 'ignore' },
|
|
71
|
+
{ name: 'Walter', age: 80 }
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
// With specific columns
|
|
75
|
+
await db`INSERT INTO users ${sql(users, 'name', 'age')}`
|
|
76
|
+
// → INSERT INTO users (name, age) VALUES (?, ?), (?, ?)
|
|
77
|
+
// → args: ["Murray", 68, "Walter", 80]
|
|
78
|
+
|
|
79
|
+
// Using all keys from first object
|
|
80
|
+
await db`INSERT INTO users ${sql(users)}`
|
|
81
|
+
// → INSERT INTO users (name, age, garbage) VALUES (?, ?, ?), (?, ?, ?)
|
|
82
|
+
// → args: ["Murray", 68, "ignore", "Walter", 80, undefined]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Example 5: Dynamic Column Updates
|
|
87
|
+
*/
|
|
88
|
+
async function example5_dynamicUpdate() {
|
|
89
|
+
const user = {
|
|
90
|
+
id: 1,
|
|
91
|
+
name: 'Murray',
|
|
92
|
+
age: 68
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Using set() helper with specific columns
|
|
96
|
+
await db`
|
|
97
|
+
UPDATE users
|
|
98
|
+
SET ${set(user, 'name', 'age')}
|
|
99
|
+
WHERE user_id = ${user.id}
|
|
100
|
+
`
|
|
101
|
+
// → UPDATE users SET name = ?, age = ? WHERE user_id = ?
|
|
102
|
+
// → args: ["Murray", 68, 1]
|
|
103
|
+
|
|
104
|
+
// Using set() with object (all keys)
|
|
105
|
+
const updates = { name: user.name, age: user.age }
|
|
106
|
+
await db`
|
|
107
|
+
UPDATE users
|
|
108
|
+
SET ${set(updates)}
|
|
109
|
+
WHERE user_id = ${user.id}
|
|
110
|
+
`
|
|
111
|
+
// → UPDATE users SET name = ?, age = ? WHERE user_id = ?
|
|
112
|
+
|
|
113
|
+
// Columns can also be given with an array
|
|
114
|
+
const columns = ['name', 'age']
|
|
115
|
+
await db`
|
|
116
|
+
UPDATE users
|
|
117
|
+
SET ${set(user, ...columns)}
|
|
118
|
+
WHERE user_id = ${user.id}
|
|
119
|
+
`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Example 6: Multiple Updates in One Query
|
|
124
|
+
*/
|
|
125
|
+
async function example6_multipleUpdates() {
|
|
126
|
+
const users = [
|
|
127
|
+
[1, 'John', 34],
|
|
128
|
+
[2, 'Jane', 27],
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
await db`
|
|
132
|
+
UPDATE users
|
|
133
|
+
SET name = update_data.name, age = (update_data.age)::int
|
|
134
|
+
FROM (VALUES ${sql(users)}) AS update_data (id, name, age)
|
|
135
|
+
WHERE users.id = (update_data.id)::int
|
|
136
|
+
RETURNING users.id, users.name, users.age
|
|
137
|
+
`
|
|
138
|
+
// → UPDATE users SET name = update_data.name, age = (update_data.age)::int
|
|
139
|
+
// FROM (VALUES (?, ?, ?), (?, ?, ?)) AS update_data (id, name, age)
|
|
140
|
+
// WHERE users.id = (update_data.id)::int
|
|
141
|
+
// RETURNING users.id, users.name, users.age
|
|
142
|
+
// → args: [1, "John", 34, 2, "Jane", 27]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Example 7: Dynamic WHERE IN Clause
|
|
147
|
+
*/
|
|
148
|
+
async function example7_whereIn() {
|
|
149
|
+
// Using sql() wrapper
|
|
150
|
+
const users1 = await db`
|
|
151
|
+
SELECT *
|
|
152
|
+
FROM users
|
|
153
|
+
WHERE age IN ${sql([68, 75, 23])}
|
|
154
|
+
`
|
|
155
|
+
// → SELECT * FROM users WHERE age IN (?, ?, ?)
|
|
156
|
+
// → args: [68, 75, 23]
|
|
157
|
+
|
|
158
|
+
// Using array directly (auto-converted)
|
|
159
|
+
const users2 = await db`
|
|
160
|
+
SELECT *
|
|
161
|
+
FROM users
|
|
162
|
+
WHERE age IN ${[68, 75, 23]}
|
|
163
|
+
`
|
|
164
|
+
// → SELECT * FROM users WHERE age IN (?, ?, ?)
|
|
165
|
+
// → args: [68, 75, 23]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Example 8: Dynamic Values in SELECT
|
|
170
|
+
*/
|
|
171
|
+
async function example8_dynamicValues() {
|
|
172
|
+
const [{ a, b, c }] = await db`
|
|
173
|
+
SELECT *
|
|
174
|
+
FROM (VALUES ${sql(['a', 'b', 'c'])}) AS x(a, b, c)
|
|
175
|
+
`
|
|
176
|
+
// Note: sql(['a', 'b', 'c']) produces "a, b, c" (column list)
|
|
177
|
+
// For VALUES with actual data, use 2D array or direct template
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Example 9: Conditional Fragments
|
|
182
|
+
*/
|
|
183
|
+
async function example9_conditionalFragments() {
|
|
184
|
+
const isAdmin = true
|
|
185
|
+
const minAge = 25
|
|
186
|
+
|
|
187
|
+
await db`
|
|
188
|
+
SELECT * FROM users
|
|
189
|
+
WHERE is_active = 1
|
|
190
|
+
${isAdmin ? fragment`AND role = 'admin'` : empty()}
|
|
191
|
+
${minAge ? fragment`AND age >= ${minAge}` : empty()}
|
|
192
|
+
`
|
|
193
|
+
// → SELECT * FROM users WHERE is_active = 1 AND role = 'admin' AND age >= ?
|
|
194
|
+
// → args: [25]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Example 10: Transactions
|
|
199
|
+
*/
|
|
200
|
+
async function example10_transactions() {
|
|
201
|
+
const user = { name: 'John', email: 'john@example.com' }
|
|
202
|
+
const userId = 1
|
|
203
|
+
const amount = 100
|
|
204
|
+
|
|
205
|
+
await db.begin(async tx => {
|
|
206
|
+
await tx`INSERT INTO users ${sql(user, 'name', 'email')}`
|
|
207
|
+
await tx`UPDATE accounts SET balance = balance - ${amount} WHERE user_id = ${userId}`
|
|
208
|
+
})
|
|
209
|
+
// Automatically commits on success, rolls back on error
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Example 11: Raw SQL (Dangerous!)
|
|
214
|
+
*/
|
|
215
|
+
async function example11_rawSql() {
|
|
216
|
+
const tableName = 'users' // From config, not user input!
|
|
217
|
+
const userId = 1
|
|
218
|
+
|
|
219
|
+
await db`SELECT * FROM ${raw(tableName)} WHERE id = ${userId}`
|
|
220
|
+
// → SELECT * FROM users WHERE id = ?
|
|
221
|
+
// → args: [1]
|
|
222
|
+
|
|
223
|
+
// ❌ NEVER DO THIS with user input:
|
|
224
|
+
// const userTable = req.query.table // Dangerous!
|
|
225
|
+
// await db`SELECT * FROM ${raw(userTable)}` // SQL injection!
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Example 12: Reusable Fragments
|
|
230
|
+
*/
|
|
231
|
+
async function example12_reusableFragments() {
|
|
232
|
+
// Static fragment
|
|
233
|
+
const activeUsers = fragment`is_active = 1 AND is_deleted = 0`
|
|
234
|
+
|
|
235
|
+
// Parameterized fragment function
|
|
236
|
+
const olderThan = (age: number) => fragment`age > ${age}`
|
|
237
|
+
const youngerThan = (age: number) => fragment`age < ${age}`
|
|
238
|
+
|
|
239
|
+
await db`
|
|
240
|
+
SELECT * FROM users
|
|
241
|
+
WHERE ${activeUsers}
|
|
242
|
+
AND ${olderThan(25)}
|
|
243
|
+
`
|
|
244
|
+
// → SELECT * FROM users WHERE is_active = 1 AND is_deleted = 0 AND age > ?
|
|
245
|
+
// → args: [25]
|
|
246
|
+
|
|
247
|
+
// Complex reusable conditions
|
|
248
|
+
const validUser = fragment`
|
|
249
|
+
is_active = 1
|
|
250
|
+
AND is_deleted = 0
|
|
251
|
+
AND is_verified = 1
|
|
252
|
+
AND banned_at IS NULL
|
|
253
|
+
`
|
|
254
|
+
|
|
255
|
+
const adminUser = fragment`${validUser} AND role = 'admin'`
|
|
256
|
+
|
|
257
|
+
await db`SELECT * FROM users WHERE ${adminUser}`
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Example 13: Combining Fragments with join()
|
|
262
|
+
*/
|
|
263
|
+
async function example13_joiningFragments() {
|
|
264
|
+
const conditions = [
|
|
265
|
+
fragment`age > ${25}`,
|
|
266
|
+
fragment`country = ${'US'}`,
|
|
267
|
+
fragment`is_active = 1`
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
await db`
|
|
271
|
+
SELECT * FROM users
|
|
272
|
+
WHERE ${join(conditions, ' AND ')}
|
|
273
|
+
`
|
|
274
|
+
// → SELECT * FROM users WHERE age > ? AND country = ? AND is_active = 1
|
|
275
|
+
// → args: [25, "US"]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Example 14: WHERE Helper
|
|
280
|
+
*/
|
|
281
|
+
async function example14_whereHelper() {
|
|
282
|
+
const filters = { is_active: 1, role: 'admin', age: 30 }
|
|
283
|
+
|
|
284
|
+
await db`
|
|
285
|
+
SELECT * FROM users
|
|
286
|
+
WHERE ${where(filters)}
|
|
287
|
+
`
|
|
288
|
+
// → SELECT * FROM users WHERE is_active = ? AND role = ? AND age = ?
|
|
289
|
+
// → args: [1, "admin", 30]
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Example 15: Complex Real-World Example
|
|
294
|
+
*/
|
|
295
|
+
async function example15_complexRealWorld() {
|
|
296
|
+
const searchTerm = 'john'
|
|
297
|
+
const filters = {
|
|
298
|
+
is_active: 1,
|
|
299
|
+
is_deleted: 0
|
|
300
|
+
}
|
|
301
|
+
const minAge = 25
|
|
302
|
+
const maxAge = 65
|
|
303
|
+
const roles = ['admin', 'moderator']
|
|
304
|
+
const sortBy = 'created_at'
|
|
305
|
+
|
|
306
|
+
await db`
|
|
307
|
+
SELECT ${sql(['id', 'name', 'email', 'role', 'created_at'])}
|
|
308
|
+
FROM users
|
|
309
|
+
WHERE ${where(filters)}
|
|
310
|
+
${searchTerm ? fragment`AND (name LIKE ${'%' + searchTerm + '%'} OR email LIKE ${'%' + searchTerm + '%'})` : empty()}
|
|
311
|
+
${minAge ? fragment`AND age >= ${minAge}` : empty()}
|
|
312
|
+
${maxAge ? fragment`AND age <= ${maxAge}` : empty()}
|
|
313
|
+
${roles.length > 0 ? fragment`AND role IN ${roles}` : empty()}
|
|
314
|
+
ORDER BY ${raw(sortBy)} DESC
|
|
315
|
+
LIMIT 100
|
|
316
|
+
`
|
|
317
|
+
// → SELECT id, name, email, role, created_at FROM users
|
|
318
|
+
// WHERE is_active = ? AND is_deleted = ?
|
|
319
|
+
// AND (name LIKE ? OR email LIKE ?)
|
|
320
|
+
// AND age >= ?
|
|
321
|
+
// AND age <= ?
|
|
322
|
+
// AND role IN (?, ?)
|
|
323
|
+
// ORDER BY created_at DESC
|
|
324
|
+
// LIMIT 100
|
|
325
|
+
// → args: [1, 0, "%john%", "%john%", 25, 65, "admin", "moderator"]
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Example 16: Batch Operations
|
|
330
|
+
*/
|
|
331
|
+
async function example16_batchOperations() {
|
|
332
|
+
// Batch insert with 2D array
|
|
333
|
+
const userData = [
|
|
334
|
+
[1, 'John', 30],
|
|
335
|
+
[2, 'Jane', 25],
|
|
336
|
+
[3, 'Bob', 35]
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
await db`
|
|
340
|
+
INSERT INTO users (id, name, age)
|
|
341
|
+
VALUES ${sql(userData)}
|
|
342
|
+
`
|
|
343
|
+
// → INSERT INTO users (id, name, age) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)
|
|
344
|
+
// → args: [1, "John", 30, 2, "Jane", 25, 3, "Bob", 35]
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* All examples demonstrate postgres.js-compatible patterns:
|
|
349
|
+
*
|
|
350
|
+
* ✅ Template literals with automatic parameterization
|
|
351
|
+
* ✅ sql(columns[]) for dynamic column selection
|
|
352
|
+
* ✅ sql(object, ...keys) for INSERT statements
|
|
353
|
+
* ✅ sql(objects[], ...keys) for bulk INSERT
|
|
354
|
+
* ✅ sql(array[][]) for VALUES clause
|
|
355
|
+
* ✅ Array values for IN clauses
|
|
356
|
+
* ✅ set(object, ...keys) for UPDATE SET clauses
|
|
357
|
+
* ✅ where(object) for WHERE conditions
|
|
358
|
+
* ✅ fragment`` for reusable query parts
|
|
359
|
+
* ✅ empty() for conditional fragments
|
|
360
|
+
* ✅ raw() for unescaped SQL (use carefully!)
|
|
361
|
+
* ✅ join(fragments[], separator) for combining fragments
|
|
362
|
+
* ✅ Transactions with begin()
|
|
363
|
+
*/
|