driftsql 1.0.15 → 1.0.17

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/.prettierrc ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "printWidth": 200,
3
+ "semi": false,
4
+ "singleQuote": true
5
+ }
package/package.json CHANGED
@@ -1,21 +1,7 @@
1
1
  {
2
2
  "name": "driftsql",
3
- "version": "1.0.15",
4
- "author": "lasse vestergaard",
3
+ "version": "1.0.17",
5
4
  "description": "A lightweight SQL client for TypeScript",
6
- "repository": "lassejlv/driftsql",
7
- "license": "MIT",
8
- "sideEffects": false,
9
- "type": "module",
10
- "exports": {
11
- ".": {
12
- "types": "./dist/index.d.mts",
13
- "default": "./dist/index.mjs"
14
- }
15
- },
16
- "files": [
17
- "dist"
18
- ],
19
5
  "scripts": {
20
6
  "build": "unbuild",
21
7
  "dev": "vitest dev",
@@ -26,20 +12,8 @@
26
12
  "test": "pnpm lint && pnpm test:types && vitest run --coverage",
27
13
  "test:types": "tsc --noEmit --skipLibCheck"
28
14
  },
29
- "devDependencies": {
30
- "@types/better-sqlite3": "^7.6.13",
31
- "@types/node": "^22.15.34",
32
- "@vitest/coverage-v8": "^3.2.4",
33
- "automd": "^0.4.0",
34
- "changelogen": "^0.6.1",
35
- "eslint": "^9.30.0",
36
- "eslint-config-unjs": "^0.4.2",
37
- "prettier": "^3.6.2",
38
- "typescript": "^5.8.3",
39
- "unbuild": "^3.5.0",
40
- "vitest": "^3.2.4"
41
- },
42
- "packageManager": "pnpm@10.12.1",
15
+ "author": "lasse vestergaard",
16
+ "license": "MIT",
43
17
  "dependencies": {
44
18
  "@libsql/client": "^0.15.9",
45
19
  "@neondatabase/serverless": "^1.0.1",
@@ -54,5 +28,19 @@
54
28
  "mysql2": "^3.14.1",
55
29
  "pg": "^8.16.3",
56
30
  "postgres": "^3.4.7"
31
+ },
32
+ "devDependencies": {
33
+ "@types/better-sqlite3": "^7.6.13",
34
+ "@types/bun": "^1.2.20",
35
+ "@types/node": "^22.15.34",
36
+ "@vitest/coverage-v8": "^3.2.4",
37
+ "automd": "^0.4.0",
38
+ "changelogen": "^0.6.1",
39
+ "eslint": "^9.30.0",
40
+ "eslint-config-unjs": "^0.4.2",
41
+ "prettier": "^3.6.2",
42
+ "typescript": "^5.8.3",
43
+ "unbuild": "^3.5.0",
44
+ "vitest": "^3.2.4"
57
45
  }
58
46
  }
@@ -0,0 +1,70 @@
1
+ import { createClient, type ResultSet } from '@libsql/client'
2
+ import { createClient as tursoServerLessClient, type ResultSet as tursoServerLessResultSet } from '@tursodatabase/serverless/compat'
3
+ import type { DatabaseDriver, QueryResult, TransactionCapable } from '../types'
4
+ import { QueryError, ConnectionError } from '../types'
5
+
6
+ export interface LibSQLConfig {
7
+ url: string
8
+ authToken?: string
9
+ useTursoServerlessDriver?: boolean
10
+ }
11
+
12
+ export class LibSQLDriver implements DatabaseDriver, TransactionCapable {
13
+ private client: ReturnType<typeof createClient> | ReturnType<typeof tursoServerLessClient>
14
+
15
+ constructor(config: LibSQLConfig) {
16
+ try {
17
+ this.client = config.useTursoServerlessDriver
18
+ ? tursoServerLessClient({
19
+ url: config.url,
20
+ ...(config.authToken ? { authToken: config.authToken } : {}),
21
+ })
22
+ : createClient({
23
+ url: config.url,
24
+ ...(config.authToken ? { authToken: config.authToken } : {}),
25
+ })
26
+ } catch (error) {
27
+ throw new ConnectionError('libsql', error as Error)
28
+ }
29
+ }
30
+
31
+ async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
32
+ try {
33
+ const result = await this.client.execute(sql, params)
34
+ return this.convertLibsqlResult<T>(result)
35
+ } catch (error) {
36
+ throw new QueryError('libsql', sql, error as Error)
37
+ }
38
+ }
39
+
40
+ async transaction<T>(callback: (driver: DatabaseDriver) => Promise<T>): Promise<T> {
41
+ const transactionDriver = new LibSQLDriver({ url: '', authToken: '' })
42
+ ;(transactionDriver as any).client = this.client
43
+ return await callback(transactionDriver)
44
+ }
45
+
46
+ async close(): Promise<void> {
47
+ try {
48
+ this.client.close()
49
+ } catch (error) {
50
+ console.error('Error closing LibSQL client:', error)
51
+ }
52
+ }
53
+
54
+ private convertLibsqlResult<T = any>(result: ResultSet | tursoServerLessResultSet): QueryResult<T> {
55
+ const rows = result.rows.map((row) => {
56
+ const obj: Record<string, any> = {}
57
+ result.columns.forEach((col, index) => {
58
+ obj[col] = row[index]
59
+ })
60
+ return obj as T
61
+ })
62
+
63
+ return {
64
+ rows,
65
+ rowCount: result.rowsAffected || rows.length,
66
+ command: undefined,
67
+ fields: result.columns.map((col) => ({ name: col, dataTypeID: 0 })),
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,70 @@
1
+ import consola from 'consola'
2
+ import * as mysql from 'mysql2/promise'
3
+ import type { DatabaseDriver, QueryResult, TransactionCapable } from '../types'
4
+ import { QueryError, ConnectionError } from '../types'
5
+
6
+ export interface MySQLConfig {
7
+ connectionString: string
8
+ }
9
+
10
+ export class MySQLDriver implements DatabaseDriver, TransactionCapable {
11
+ private client: ReturnType<typeof mysql.createConnection>
12
+
13
+ constructor(config: MySQLConfig) {
14
+ consola.warn('MySQL client is experimental and may not be compatible with the helper functions, since they originally designed for PostgreSQL and libsql. But .query() method should work.')
15
+
16
+ try {
17
+ this.client = mysql.createConnection(config.connectionString)
18
+ } catch (error) {
19
+ throw new ConnectionError('mysql', error as Error)
20
+ }
21
+ }
22
+
23
+ async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
24
+ try {
25
+ const [rows, fields] = await (await this.client).execute(sql, params || [])
26
+ const rowCount = Array.isArray(rows) ? rows.length : 0
27
+
28
+ const normalizedFields = fields.map((field: any) => ({
29
+ name: field.name,
30
+ dataTypeID: field.columnType,
31
+ }))
32
+
33
+ return {
34
+ rows: rows as T[],
35
+ rowCount,
36
+ command: undefined,
37
+ fields: normalizedFields,
38
+ }
39
+ } catch (error) {
40
+ throw new QueryError('mysql', sql, error as Error)
41
+ }
42
+ }
43
+
44
+ async transaction<T>(callback: (driver: DatabaseDriver) => Promise<T>): Promise<T> {
45
+ const connection = await this.client
46
+
47
+ try {
48
+ await connection.beginTransaction()
49
+
50
+ const transactionDriver = new MySQLDriver({ connectionString: '' })
51
+ transactionDriver.client = Promise.resolve(connection)
52
+
53
+ const result = await callback(transactionDriver)
54
+
55
+ await connection.commit()
56
+ return result
57
+ } catch (error) {
58
+ await connection.rollback()
59
+ throw error
60
+ }
61
+ }
62
+
63
+ async close(): Promise<void> {
64
+ try {
65
+ await (await this.client).end()
66
+ } catch (error) {
67
+ consola.error('Error closing MySQL client:', error)
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,112 @@
1
+ import postgres from 'postgres'
2
+ import ky from 'ky'
3
+ import type { DatabaseDriver, QueryResult, TransactionCapable } from '../types'
4
+ import { QueryError, ConnectionError } from '../types'
5
+
6
+ export interface PostgresConfig {
7
+ connectionString?: string
8
+ experimental?: {
9
+ http?: {
10
+ url: string
11
+ apiKey?: string
12
+ }
13
+ }
14
+ }
15
+
16
+ export class PostgresDriver implements DatabaseDriver, TransactionCapable {
17
+ private client: postgres.Sql | PostgresHTTPDriver
18
+
19
+ constructor(config: PostgresConfig) {
20
+ try {
21
+ if (config.experimental?.http) {
22
+ this.client = new PostgresHTTPDriver(config.experimental.http)
23
+ } else {
24
+ this.client = postgres(config.connectionString || '')
25
+ }
26
+ } catch (error) {
27
+ throw new ConnectionError('postgres', error as Error)
28
+ }
29
+ }
30
+
31
+ async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
32
+ try {
33
+ if (this.client instanceof PostgresHTTPDriver) {
34
+ return await this.client.query<T>(sql, params)
35
+ }
36
+
37
+ const result = await this.client.unsafe(sql, params || [])
38
+ return {
39
+ rows: result as unknown as T[],
40
+ rowCount: Array.isArray(result) ? result.length : 0,
41
+ command: undefined,
42
+ }
43
+ } catch (error) {
44
+ throw new QueryError('postgres', sql, error as Error)
45
+ }
46
+ }
47
+
48
+ async transaction<T>(callback: (driver: DatabaseDriver) => Promise<T>): Promise<T> {
49
+ if (this.client instanceof PostgresHTTPDriver) {
50
+ throw new Error('Transactions not supported with HTTP driver')
51
+ }
52
+
53
+ const result = await this.client.begin(async (sql) => {
54
+ const transactionDriver = new PostgresDriver({ connectionString: '' })
55
+ transactionDriver.client = sql
56
+ return await callback(transactionDriver)
57
+ })
58
+ return result as T
59
+ }
60
+
61
+ // Helper methods for findMany, findFirst, insert, update, delete
62
+ async findMany<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
63
+ return this.query<T>(sql, params)
64
+ }
65
+
66
+ async close(): Promise<void> {
67
+ try {
68
+ if (this.client instanceof PostgresHTTPDriver) {
69
+ return await this.client.close()
70
+ }
71
+ await this.client.end()
72
+ } catch (error) {
73
+ console.error('Error closing Postgres client:', error)
74
+ }
75
+ }
76
+ }
77
+
78
+ class PostgresHTTPDriver {
79
+ private httpClient: typeof ky
80
+
81
+ constructor(config: { url: string; apiKey?: string }) {
82
+ this.httpClient = ky.create({
83
+ prefixUrl: config.url,
84
+ headers: {
85
+ Authorization: `Bearer ${config.apiKey || ''}`,
86
+ },
87
+ })
88
+ }
89
+
90
+ async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
91
+ try {
92
+ const response = await this.httpClient
93
+ .post('query', {
94
+ json: { query: sql, args: params },
95
+ })
96
+ .json<{ rows: T[]; rowCount: number }>()
97
+
98
+ return {
99
+ rows: response.rows,
100
+ rowCount: response.rowCount,
101
+ command: undefined,
102
+ }
103
+ } catch (error) {
104
+ throw new QueryError('postgres-http', sql, error as Error)
105
+ }
106
+ }
107
+
108
+ async close(): Promise<void> {
109
+ // HTTP connections don't need explicit closing
110
+ return Promise.resolve()
111
+ }
112
+ }
@@ -0,0 +1,122 @@
1
+ import Database from 'better-sqlite3'
2
+ import type { DatabaseDriver, QueryResult, TransactionCapable, PreparedStatementCapable, PreparedStatement } from '../types'
3
+ import { QueryError, ConnectionError } from '../types'
4
+
5
+ export interface SqliteConfig {
6
+ filename: string
7
+ readonly?: boolean
8
+ }
9
+
10
+ export class SqliteDriver implements DatabaseDriver, TransactionCapable, PreparedStatementCapable {
11
+ private client: Database.Database
12
+
13
+ constructor(config: SqliteConfig) {
14
+ try {
15
+ this.client = new Database(config.filename, {
16
+ readonly: config.readonly || false,
17
+ fileMustExist: config.readonly || false,
18
+ })
19
+ } catch (error) {
20
+ throw new ConnectionError('sqlite', error as Error)
21
+ }
22
+ }
23
+
24
+ async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
25
+ try {
26
+ const stmt = this.client.prepare(sql)
27
+ const rows = stmt.all(params || []) as T[]
28
+
29
+ // Get column names from the first row if available
30
+ const fields = rows.length > 0 && typeof rows[0] === 'object' && rows[0] !== null ? Object.keys(rows[0] as object).map((name) => ({ name, dataTypeID: 0 })) : []
31
+
32
+ return {
33
+ rows,
34
+ rowCount: rows.length,
35
+ command: undefined,
36
+ fields,
37
+ }
38
+ } catch (error) {
39
+ throw new QueryError('sqlite', sql, error as Error)
40
+ }
41
+ }
42
+
43
+ async transaction<T>(callback: (driver: DatabaseDriver) => Promise<T>): Promise<T> {
44
+ const transaction = this.client.transaction(() => {
45
+ const transactionDriver = new SqliteDriver({ filename: '' })
46
+ ;(transactionDriver as any).client = this.client
47
+ return callback(transactionDriver)
48
+ })
49
+
50
+ return await transaction()
51
+ }
52
+
53
+ async prepare(sql: string): Promise<PreparedStatement> {
54
+ return new SqlitePreparedStatement(this.client.prepare(sql))
55
+ }
56
+
57
+ async close(): Promise<void> {
58
+ try {
59
+ this.client.close()
60
+ } catch (error) {
61
+ console.error('Error closing SQLite client:', error)
62
+ }
63
+ }
64
+
65
+ // SQLite-specific methods
66
+ exec(sql: string): void {
67
+ this.client.exec(sql)
68
+ }
69
+
70
+ backup(filename: string): Promise<void> {
71
+ return new Promise((resolve, reject) => {
72
+ try {
73
+ this.client.backup(filename)
74
+ resolve()
75
+ } catch (error) {
76
+ reject(error)
77
+ }
78
+ })
79
+ }
80
+
81
+ pragma(pragma: string): any {
82
+ return this.client.pragma(pragma)
83
+ }
84
+ }
85
+
86
+ class SqlitePreparedStatement implements PreparedStatement {
87
+ constructor(private stmt: Database.Statement) {}
88
+
89
+ async execute<T = any>(params?: any[]): Promise<QueryResult<T>> {
90
+ try {
91
+ const rows = this.stmt.all(params || []) as T[]
92
+ const fields = rows.length > 0 && typeof rows[0] === 'object' && rows[0] !== null ? Object.keys(rows[0] as object).map((name) => ({ name, dataTypeID: 0 })) : []
93
+
94
+ return {
95
+ rows,
96
+ rowCount: rows.length,
97
+ command: undefined,
98
+ fields,
99
+ }
100
+ } catch (error) {
101
+ throw new QueryError('sqlite', 'prepared statement', error as Error)
102
+ }
103
+ }
104
+
105
+ async finalize(): Promise<void> {
106
+ // better-sqlite3 doesn't have finalize method, we can just ignore this
107
+ return Promise.resolve()
108
+ }
109
+
110
+ // SQLite-specific methods for prepared statements
111
+ run(params?: any[]): Database.RunResult {
112
+ return this.stmt.run(params || [])
113
+ }
114
+
115
+ get(params?: any[]): any {
116
+ return this.stmt.get(params || [])
117
+ }
118
+
119
+ all(params?: any[]): any[] {
120
+ return this.stmt.all(params || [])
121
+ }
122
+ }
package/src/index.ts ADDED
@@ -0,0 +1,225 @@
1
+ import consola from 'consola'
2
+ import type { DatabaseDriver, QueryResult } from './types'
3
+ import { hasTransactionSupport, hasPreparedStatementSupport, DatabaseError } from './types'
4
+ import { PostgresDriver } from './drivers/postgres'
5
+ import { LibSQLDriver } from './drivers/libsql'
6
+ import { MySQLDriver } from './drivers/mysql'
7
+ import { SqliteDriver } from './drivers/sqlite'
8
+
9
+ // Re-export types and drivers for convenience
10
+ export type { DatabaseDriver, QueryResult, QueryError, QueryField, ConnectionError } from './types'
11
+ export { PostgresDriver } from './drivers/postgres'
12
+ export { LibSQLDriver } from './drivers/libsql'
13
+ export { MySQLDriver } from './drivers/mysql'
14
+ export { SqliteDriver } from './drivers/sqlite'
15
+
16
+ // Re-export inspection utilities
17
+ export { inspectDB, inspectPostgres, inspectLibSQL, inspectMySQL, inspectSQLite } from './pull'
18
+
19
+ export interface ClientOptions<T extends DatabaseDriver = DatabaseDriver> {
20
+ driver: T
21
+ fallbackDrivers?: DatabaseDriver[]
22
+ }
23
+
24
+ export class SQLClient<DT = any> {
25
+ private primaryDriver: DatabaseDriver
26
+ private fallbackDrivers: DatabaseDriver[]
27
+
28
+ constructor(options: ClientOptions) {
29
+ this.primaryDriver = options.driver
30
+ this.fallbackDrivers = options.fallbackDrivers || []
31
+ }
32
+
33
+ async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
34
+ const drivers = [this.primaryDriver, ...this.fallbackDrivers]
35
+ let lastError: Error | undefined
36
+
37
+ for (const driver of drivers) {
38
+ try {
39
+ return await driver.query<T>(sql, params)
40
+ } catch (error) {
41
+ lastError = error as Error
42
+ consola.warn(`Query failed with ${driver.constructor.name}:`, error)
43
+ continue
44
+ }
45
+ }
46
+
47
+ throw lastError || new DatabaseError('All drivers failed to execute query', 'unknown')
48
+ }
49
+
50
+ async transaction<T>(callback: (client: SQLClient<DT>) => Promise<T>): Promise<T> {
51
+ if (!hasTransactionSupport(this.primaryDriver)) {
52
+ throw new DatabaseError('Primary driver does not support transactions', this.primaryDriver.constructor.name)
53
+ }
54
+
55
+ return await this.primaryDriver.transaction(async (transactionDriver) => {
56
+ const transactionClient = new SQLClient<DT>({
57
+ driver: transactionDriver,
58
+ fallbackDrivers: [],
59
+ })
60
+ return await callback(transactionClient)
61
+ })
62
+ }
63
+
64
+ async prepare(sql: string) {
65
+ if (!hasPreparedStatementSupport(this.primaryDriver)) {
66
+ throw new DatabaseError('Primary driver does not support prepared statements', this.primaryDriver.constructor.name)
67
+ }
68
+
69
+ return await this.primaryDriver.prepare(sql)
70
+ }
71
+
72
+ // Helper methods for common database operations
73
+ async findFirst<K extends keyof DT>(table: K, where?: Partial<DT[K]>): Promise<DT[K] | null> {
74
+ const tableName = String(table)
75
+ const whereEntries = Object.entries(where || {})
76
+
77
+ let sql = `SELECT * FROM ${tableName}`
78
+ let params: any[] = []
79
+
80
+ if (whereEntries.length > 0) {
81
+ const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(' AND ')
82
+ sql += ` WHERE ${whereClause}`
83
+ params = whereEntries.map(([, value]) => value)
84
+ }
85
+
86
+ sql += ' LIMIT 1'
87
+
88
+ const result = await this.query<DT[K]>(sql, params)
89
+ return result.rows[0] || null
90
+ }
91
+
92
+ async findMany<K extends keyof DT>(
93
+ table: K,
94
+ options?: {
95
+ where?: Partial<DT[K]>
96
+ limit?: number
97
+ offset?: number
98
+ },
99
+ ): Promise<DT[K][]> {
100
+ const tableName = String(table)
101
+ const { where, limit, offset } = options || {}
102
+ const whereEntries = Object.entries(where || {})
103
+
104
+ let sql = `SELECT * FROM ${tableName}`
105
+ let params: any[] = []
106
+
107
+ if (whereEntries.length > 0) {
108
+ const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(' AND ')
109
+ sql += ` WHERE ${whereClause}`
110
+ params = whereEntries.map(([, value]) => value)
111
+ }
112
+
113
+ if (typeof limit === 'number' && limit > 0) {
114
+ sql += ` LIMIT $${params.length + 1}`
115
+ params.push(limit)
116
+ }
117
+
118
+ if (typeof offset === 'number' && offset > 0) {
119
+ sql += ` OFFSET $${params.length + 1}`
120
+ params.push(offset)
121
+ }
122
+
123
+ const result = await this.query<DT[K]>(sql, params)
124
+ return result.rows
125
+ }
126
+
127
+ async insert<K extends keyof DT>(table: K, data: Partial<DT[K]>): Promise<DT[K]> {
128
+ const tableName = String(table)
129
+ const keys = Object.keys(data)
130
+ const values = Object.values(data)
131
+
132
+ if (keys.length === 0) {
133
+ throw new Error('No data provided for insert')
134
+ }
135
+
136
+ const placeholders = keys.map((_, index) => `$${index + 1}`).join(', ')
137
+ const sql = `INSERT INTO ${tableName} (${keys.join(', ')}) VALUES (${placeholders}) RETURNING *`
138
+
139
+ const result = await this.query<DT[K]>(sql, values)
140
+
141
+ if (!result.rows[0]) {
142
+ throw new Error('Insert failed: No data returned')
143
+ }
144
+
145
+ return result.rows[0]
146
+ }
147
+
148
+ async update<K extends keyof DT>(table: K, data: Partial<DT[K]>, where: Partial<DT[K]>): Promise<DT[K] | null> {
149
+ const tableName = String(table)
150
+ const setEntries = Object.entries(data)
151
+ const whereEntries = Object.entries(where)
152
+
153
+ if (setEntries.length === 0) {
154
+ throw new Error('No data provided for update')
155
+ }
156
+
157
+ if (whereEntries.length === 0) {
158
+ throw new Error('No conditions provided for update')
159
+ }
160
+
161
+ const setClause = setEntries.map((_, index) => `${setEntries[index]?.[0]} = $${index + 1}`).join(', ')
162
+ const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${setEntries.length + index + 1}`).join(' AND ')
163
+
164
+ const sql = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause} RETURNING *`
165
+ const params = [...setEntries.map(([, value]) => value), ...whereEntries.map(([, value]) => value)]
166
+
167
+ const result = await this.query<DT[K]>(sql, params)
168
+ return result.rows[0] || null
169
+ }
170
+
171
+ async delete<K extends keyof DT>(table: K, where: Partial<DT[K]>): Promise<number> {
172
+ const tableName = String(table)
173
+ const whereEntries = Object.entries(where)
174
+
175
+ if (whereEntries.length === 0) {
176
+ throw new Error('No conditions provided for delete')
177
+ }
178
+
179
+ const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(' AND ')
180
+ const sql = `DELETE FROM ${tableName} WHERE ${whereClause}`
181
+ const params = whereEntries.map(([, value]) => value)
182
+
183
+ const result = await this.query<DT[K]>(sql, params)
184
+ return result.rowCount || 0
185
+ }
186
+
187
+ // Get the primary driver (useful for driver-specific operations)
188
+ getDriver(): DatabaseDriver {
189
+ return this.primaryDriver
190
+ }
191
+
192
+ // Check driver capabilities
193
+ supportsTransactions(): boolean {
194
+ return hasTransactionSupport(this.primaryDriver)
195
+ }
196
+
197
+ supportsPreparedStatements(): boolean {
198
+ return hasPreparedStatementSupport(this.primaryDriver)
199
+ }
200
+
201
+ async close(): Promise<void> {
202
+ const drivers = [this.primaryDriver, ...this.fallbackDrivers]
203
+ await Promise.all(drivers.map((driver) => driver.close().catch((err) => consola.warn(`Error closing ${driver.constructor.name}:`, err))))
204
+ }
205
+ }
206
+
207
+ // Factory functions for common use cases
208
+ export function createPostgresClient<DT = any>(config: { connectionString?: string; experimental?: { http?: { url: string; apiKey?: string } } }) {
209
+ return new SQLClient<DT>({ driver: new PostgresDriver(config) })
210
+ }
211
+
212
+ export function createLibSQLClient<DT = any>(config: { url: string; authToken?: string; useTursoServerlessDriver?: boolean }) {
213
+ return new SQLClient<DT>({ driver: new LibSQLDriver(config) })
214
+ }
215
+
216
+ export function createMySQLClient<DT = any>(config: { connectionString: string }) {
217
+ return new SQLClient<DT>({ driver: new MySQLDriver(config) })
218
+ }
219
+
220
+ export function createSqliteClient<DT = any>(config: { filename: string; readonly?: boolean }) {
221
+ return new SQLClient<DT>({ driver: new SqliteDriver(config) })
222
+ }
223
+
224
+ // Legacy export for backward compatibility
225
+ export const DriftSQLClient = SQLClient