driftsql 1.0.18 → 1.0.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/dist/drivers/libsql.d.ts +23 -0
- package/dist/drivers/mysql.d.ts +20 -0
- package/dist/drivers/postgres.d.ts +26 -0
- package/dist/drivers/sqlite.d.ts +25 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +48 -0
- package/dist/index.mjs +764 -0
- package/dist/pull.d.ts +28 -0
- package/dist/types.d.ts +49 -0
- package/package.json +9 -10
- package/tsconfig.json +24 -10
- package/src/drivers/bobsql.ts +0 -43
- package/src/drivers/libsql.ts +0 -70
- package/src/drivers/mysql.ts +0 -70
- package/src/drivers/postgres.ts +0 -112
- package/src/drivers/sqlite.ts +0 -122
- package/src/index.ts +0 -225
- package/src/pull.ts +0 -318
- package/src/types.ts +0 -78
package/src/index.ts
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
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
|
-
export { BobSQLDriver, type BobSQLConfig } from './drivers/bobsql'
|
|
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
|
package/src/pull.ts
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import { PostgresDriver, LibSQLDriver, MySQLDriver, SqliteDriver, SQLClient } from '.'
|
|
2
|
-
import type { DatabaseDriver } from './types'
|
|
3
|
-
import fs from 'node:fs/promises'
|
|
4
|
-
|
|
5
|
-
interface InspectOptions {
|
|
6
|
-
driver: DatabaseDriver
|
|
7
|
-
outputFile?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Helper function to add timeout to promises
|
|
11
|
-
const withTimeout = <T>(promise: Promise<T>, timeoutMs = 30_000): Promise<T> => {
|
|
12
|
-
return Promise.race([promise, new Promise<never>((_, reject) => setTimeout(() => reject(new Error(`Query timeout after ${timeoutMs}ms`)), timeoutMs))])
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Helper function to retry queries with exponential backoff
|
|
16
|
-
const retryQuery = async <T>(queryFn: () => Promise<T>, maxRetries = 3, baseDelay = 1000): Promise<T> => {
|
|
17
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
18
|
-
try {
|
|
19
|
-
return await queryFn()
|
|
20
|
-
} catch (error) {
|
|
21
|
-
if (attempt === maxRetries) {
|
|
22
|
-
throw error
|
|
23
|
-
}
|
|
24
|
-
const delay = baseDelay * Math.pow(2, attempt - 1)
|
|
25
|
-
console.warn(`Query attempt ${attempt} failed, retrying in ${delay}ms...`, error)
|
|
26
|
-
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
throw new Error('Max retries exceeded')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const mapDatabaseTypeToTypeScript = (dataType: string, isNullable: boolean = false, driverType: string = 'postgres'): string => {
|
|
33
|
-
const nullable = isNullable ? ' | null' : ''
|
|
34
|
-
const lowerType = dataType.toLowerCase()
|
|
35
|
-
|
|
36
|
-
// Common types that work for all databases
|
|
37
|
-
switch (lowerType) {
|
|
38
|
-
case 'uuid': {
|
|
39
|
-
return `string${nullable}`
|
|
40
|
-
}
|
|
41
|
-
case 'character varying':
|
|
42
|
-
case 'varchar':
|
|
43
|
-
case 'text':
|
|
44
|
-
case 'char':
|
|
45
|
-
case 'character':
|
|
46
|
-
case 'longtext':
|
|
47
|
-
case 'mediumtext':
|
|
48
|
-
case 'tinytext': {
|
|
49
|
-
return `string${nullable}`
|
|
50
|
-
}
|
|
51
|
-
case 'integer':
|
|
52
|
-
case 'int':
|
|
53
|
-
case 'int4':
|
|
54
|
-
case 'smallint':
|
|
55
|
-
case 'int2':
|
|
56
|
-
case 'bigint':
|
|
57
|
-
case 'int8':
|
|
58
|
-
case 'serial':
|
|
59
|
-
case 'bigserial':
|
|
60
|
-
case 'numeric':
|
|
61
|
-
case 'decimal':
|
|
62
|
-
case 'real':
|
|
63
|
-
case 'float4':
|
|
64
|
-
case 'double precision':
|
|
65
|
-
case 'float8':
|
|
66
|
-
case 'tinyint':
|
|
67
|
-
case 'mediumint':
|
|
68
|
-
case 'float':
|
|
69
|
-
case 'double': {
|
|
70
|
-
return `number${nullable}`
|
|
71
|
-
}
|
|
72
|
-
case 'boolean':
|
|
73
|
-
case 'bool':
|
|
74
|
-
case 'bit': {
|
|
75
|
-
return `boolean${nullable}`
|
|
76
|
-
}
|
|
77
|
-
case 'timestamp':
|
|
78
|
-
case 'timestamp with time zone':
|
|
79
|
-
case 'timestamp without time zone':
|
|
80
|
-
case 'timestamptz':
|
|
81
|
-
case 'date':
|
|
82
|
-
case 'time':
|
|
83
|
-
case 'time with time zone':
|
|
84
|
-
case 'time without time zone':
|
|
85
|
-
case 'timetz':
|
|
86
|
-
case 'interval':
|
|
87
|
-
case 'datetime':
|
|
88
|
-
case 'year': {
|
|
89
|
-
return `Date${nullable}`
|
|
90
|
-
}
|
|
91
|
-
case 'json':
|
|
92
|
-
case 'jsonb': {
|
|
93
|
-
return `any${nullable}`
|
|
94
|
-
}
|
|
95
|
-
case 'array': {
|
|
96
|
-
return `any[]${nullable}`
|
|
97
|
-
}
|
|
98
|
-
case 'bytea':
|
|
99
|
-
case 'binary':
|
|
100
|
-
case 'varbinary':
|
|
101
|
-
case 'blob':
|
|
102
|
-
case 'longblob':
|
|
103
|
-
case 'mediumblob':
|
|
104
|
-
case 'tinyblob': {
|
|
105
|
-
return `Buffer${nullable}`
|
|
106
|
-
}
|
|
107
|
-
case 'enum':
|
|
108
|
-
case 'set': {
|
|
109
|
-
return `string${nullable}`
|
|
110
|
-
}
|
|
111
|
-
default: {
|
|
112
|
-
console.warn(`Unknown ${driverType} type: ${dataType}, defaulting to 'any'`)
|
|
113
|
-
return `any${nullable}`
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const getDriverType = (driver: DatabaseDriver): string => {
|
|
119
|
-
if (driver instanceof PostgresDriver) return 'postgres'
|
|
120
|
-
if (driver instanceof LibSQLDriver) return 'libsql'
|
|
121
|
-
if (driver instanceof MySQLDriver) return 'mysql'
|
|
122
|
-
if (driver instanceof SqliteDriver) return 'sqlite'
|
|
123
|
-
return 'unknown'
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export const inspectDB = async (options: InspectOptions) => {
|
|
127
|
-
const { driver, outputFile = 'db-types.ts' } = options
|
|
128
|
-
const driverType = getDriverType(driver)
|
|
129
|
-
|
|
130
|
-
console.log(`Inspecting database using ${driverType} driver`)
|
|
131
|
-
|
|
132
|
-
const client = new SQLClient({ driver })
|
|
133
|
-
let generatedTypes = ''
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
// Use different queries based on the driver type
|
|
137
|
-
let tablesQuery: string
|
|
138
|
-
let tableSchemaFilter: string | undefined
|
|
139
|
-
|
|
140
|
-
if (driverType === 'mysql') {
|
|
141
|
-
// For MySQL, get the current database name first
|
|
142
|
-
const dbResult = await withTimeout(
|
|
143
|
-
retryQuery(() => client.query<{ database: string }>('SELECT DATABASE() as `database`', [])),
|
|
144
|
-
10_000,
|
|
145
|
-
)
|
|
146
|
-
const currentDatabase = dbResult.rows[0]?.database
|
|
147
|
-
|
|
148
|
-
if (!currentDatabase) {
|
|
149
|
-
throw new Error('Could not determine current MySQL database name')
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(`Using MySQL database: ${currentDatabase}`)
|
|
153
|
-
tablesQuery = `SELECT TABLE_NAME as table_name
|
|
154
|
-
FROM information_schema.tables
|
|
155
|
-
WHERE TABLE_SCHEMA = ?
|
|
156
|
-
AND TABLE_TYPE = 'BASE TABLE'
|
|
157
|
-
ORDER BY TABLE_NAME`
|
|
158
|
-
tableSchemaFilter = currentDatabase
|
|
159
|
-
} else if (driverType === 'postgres') {
|
|
160
|
-
// PostgreSQL
|
|
161
|
-
tablesQuery = `SELECT table_name
|
|
162
|
-
FROM information_schema.tables
|
|
163
|
-
WHERE table_schema = $1
|
|
164
|
-
AND table_type = 'BASE TABLE'
|
|
165
|
-
ORDER BY table_name`
|
|
166
|
-
tableSchemaFilter = 'public'
|
|
167
|
-
} else if (driverType === 'libsql' || driverType === 'sqlite') {
|
|
168
|
-
// LibSQL/SQLite
|
|
169
|
-
tablesQuery = `SELECT name as table_name
|
|
170
|
-
FROM sqlite_master
|
|
171
|
-
WHERE type = 'table'
|
|
172
|
-
ORDER BY name`
|
|
173
|
-
tableSchemaFilter = undefined
|
|
174
|
-
} else {
|
|
175
|
-
throw new Error(`Unsupported driver type: ${driverType}`)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const tables = await withTimeout(
|
|
179
|
-
retryQuery(() => client.query<{ table_name: string }>(tablesQuery, tableSchemaFilter ? [tableSchemaFilter] : [])),
|
|
180
|
-
30_000,
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
console.log('Tables in the database:', tables.rows.map((t) => t.table_name).join(', '))
|
|
184
|
-
|
|
185
|
-
let processedTables = 0
|
|
186
|
-
const totalTables = tables.rows.length
|
|
187
|
-
|
|
188
|
-
for (const table of tables.rows) {
|
|
189
|
-
const tableName = table.table_name
|
|
190
|
-
processedTables++
|
|
191
|
-
console.log(`[${processedTables}/${totalTables}] Inspecting table: ${tableName}`)
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
// Get columns with nullability information
|
|
195
|
-
let columnsQuery: string
|
|
196
|
-
let queryParams: (string | null)[]
|
|
197
|
-
|
|
198
|
-
if (driverType === 'mysql') {
|
|
199
|
-
columnsQuery = `
|
|
200
|
-
SELECT
|
|
201
|
-
COLUMN_NAME as column_name,
|
|
202
|
-
DATA_TYPE as data_type,
|
|
203
|
-
IS_NULLABLE as is_nullable,
|
|
204
|
-
COLUMN_DEFAULT as column_default
|
|
205
|
-
FROM information_schema.columns
|
|
206
|
-
WHERE TABLE_NAME = ?
|
|
207
|
-
AND TABLE_SCHEMA = ?
|
|
208
|
-
ORDER BY ORDINAL_POSITION
|
|
209
|
-
`
|
|
210
|
-
queryParams = [tableName, tableSchemaFilter!]
|
|
211
|
-
} else if (driverType === 'postgres') {
|
|
212
|
-
columnsQuery = `
|
|
213
|
-
SELECT
|
|
214
|
-
column_name,
|
|
215
|
-
data_type,
|
|
216
|
-
is_nullable,
|
|
217
|
-
column_default
|
|
218
|
-
FROM information_schema.columns
|
|
219
|
-
WHERE table_name = $1
|
|
220
|
-
AND table_schema = $2
|
|
221
|
-
ORDER BY ordinal_position
|
|
222
|
-
`
|
|
223
|
-
queryParams = [tableName, tableSchemaFilter!]
|
|
224
|
-
} else {
|
|
225
|
-
// LibSQL/SQLite
|
|
226
|
-
columnsQuery = `
|
|
227
|
-
SELECT
|
|
228
|
-
name as column_name,
|
|
229
|
-
type as data_type,
|
|
230
|
-
CASE WHEN "notnull" = 0 THEN 'YES' ELSE 'NO' END as is_nullable,
|
|
231
|
-
dflt_value as column_default
|
|
232
|
-
FROM pragma_table_info(?)
|
|
233
|
-
ORDER BY cid
|
|
234
|
-
`
|
|
235
|
-
queryParams = [tableName]
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const columns = await withTimeout(
|
|
239
|
-
retryQuery(() =>
|
|
240
|
-
client.query<{
|
|
241
|
-
column_name: string
|
|
242
|
-
data_type: string
|
|
243
|
-
is_nullable: string
|
|
244
|
-
column_default: string | null
|
|
245
|
-
}>(columnsQuery, queryParams),
|
|
246
|
-
),
|
|
247
|
-
15_000,
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
if (columns.rows.length === 0) {
|
|
251
|
-
console.log(`No columns found for table: ${tableName}`)
|
|
252
|
-
continue
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
console.log(`Columns in ${tableName}:`, columns.rows.map((c) => `${c.column_name} (${c.data_type}${c.is_nullable === 'YES' ? ', nullable' : ''})`).join(', '))
|
|
256
|
-
|
|
257
|
-
// Deduplicate columns by name
|
|
258
|
-
const uniqueColumns = new Map<string, (typeof columns.rows)[0]>()
|
|
259
|
-
columns.rows.forEach((col) => {
|
|
260
|
-
if (!uniqueColumns.has(col.column_name)) {
|
|
261
|
-
uniqueColumns.set(col.column_name, col)
|
|
262
|
-
}
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
generatedTypes += `export interface ${tableName.charAt(0).toUpperCase() + tableName.slice(1)} {\n`
|
|
266
|
-
for (const col of uniqueColumns.values()) {
|
|
267
|
-
const tsType = mapDatabaseTypeToTypeScript(col.data_type, col.is_nullable === 'YES', driverType)
|
|
268
|
-
generatedTypes += ` ${col.column_name}: ${tsType};\n`
|
|
269
|
-
}
|
|
270
|
-
generatedTypes += '}\n\n'
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error(`Failed to process table ${tableName}:`, error)
|
|
273
|
-
console.log(`Skipping table ${tableName} and continuing...`)
|
|
274
|
-
continue
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Generate the Database interface
|
|
279
|
-
generatedTypes += 'export interface Database {\n'
|
|
280
|
-
for (const table of tables.rows) {
|
|
281
|
-
const interfaceName = table.table_name.charAt(0).toUpperCase() + table.table_name.slice(1)
|
|
282
|
-
generatedTypes += ` ${table.table_name}: ${interfaceName};\n`
|
|
283
|
-
}
|
|
284
|
-
generatedTypes += '}\n\n'
|
|
285
|
-
|
|
286
|
-
await fs.writeFile(outputFile, generatedTypes, 'utf8')
|
|
287
|
-
console.log(`TypeScript types written to ${outputFile}`)
|
|
288
|
-
console.log(`Successfully processed ${processedTables} tables`)
|
|
289
|
-
} catch (error) {
|
|
290
|
-
console.error('Fatal error during database inspection:', error)
|
|
291
|
-
throw error
|
|
292
|
-
} finally {
|
|
293
|
-
await client.close()
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Convenience functions for each driver type
|
|
298
|
-
export const inspectPostgres = async (config: { connectionString?: string; experimental?: { http?: { url: string; apiKey?: string } } }, outputFile?: string) => {
|
|
299
|
-
const driver = new PostgresDriver(config)
|
|
300
|
-
return inspectDB({ driver, outputFile })
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export const inspectLibSQL = async (config: { url: string; authToken?: string; useTursoServerlessDriver?: boolean }, outputFile?: string) => {
|
|
304
|
-
const driver = new LibSQLDriver(config)
|
|
305
|
-
return inspectDB({ driver, outputFile })
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export const inspectMySQL = async (config: { connectionString: string }, outputFile?: string) => {
|
|
309
|
-
const driver = new MySQLDriver(config)
|
|
310
|
-
return inspectDB({ driver, outputFile })
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export const inspectSQLite = async (config: { filename: string; readonly?: boolean }, outputFile?: string) => {
|
|
314
|
-
const driver = new SqliteDriver(config)
|
|
315
|
-
return inspectDB({ driver, outputFile })
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export default inspectDB
|
package/src/types.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// Core result type for all database operations
|
|
2
|
-
export interface QueryResult<T = any> {
|
|
3
|
-
rows: T[]
|
|
4
|
-
rowCount: number
|
|
5
|
-
command?: string
|
|
6
|
-
fields?: QueryField[]
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Field information for query results
|
|
10
|
-
export interface QueryField {
|
|
11
|
-
name: string
|
|
12
|
-
dataTypeID: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Base driver interface that all drivers must implement
|
|
16
|
-
export interface DatabaseDriver {
|
|
17
|
-
query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>>
|
|
18
|
-
// findMany?<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>>
|
|
19
|
-
// findFirst?<T = any>(sql: string, params?: any[]): Promise<T | null>
|
|
20
|
-
// insert?<T = any>(table: string, data: Partial<T>): Promise<QueryResult<T>>
|
|
21
|
-
// update?<T = any>(table: string, data: Partial<T>, where: Partial<T>): Promise<QueryResult<T>>
|
|
22
|
-
// delete?<T = any>(table: string, where: Partial<T>): Promise<QueryResult<T>>
|
|
23
|
-
close(): Promise<void>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Optional interfaces for advanced features
|
|
27
|
-
export interface TransactionCapable {
|
|
28
|
-
transaction<T>(callback: (driver: DatabaseDriver) => Promise<T>): Promise<T>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface PreparedStatementCapable {
|
|
32
|
-
prepare(sql: string): Promise<PreparedStatement>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface PreparedStatement {
|
|
36
|
-
execute<T = any>(params?: any[]): Promise<QueryResult<T>>
|
|
37
|
-
finalize(): Promise<void>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Type guards
|
|
41
|
-
export function hasTransactionSupport(driver: DatabaseDriver): driver is DatabaseDriver & TransactionCapable {
|
|
42
|
-
return 'transaction' in driver && typeof (driver as any).transaction === 'function'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function hasPreparedStatementSupport(driver: DatabaseDriver): driver is DatabaseDriver & PreparedStatementCapable {
|
|
46
|
-
return 'prepare' in driver && typeof (driver as any).prepare === 'function'
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Error classes
|
|
50
|
-
export class DatabaseError extends Error {
|
|
51
|
-
constructor(
|
|
52
|
-
message: string,
|
|
53
|
-
public readonly driverType: string,
|
|
54
|
-
public readonly originalError?: Error,
|
|
55
|
-
) {
|
|
56
|
-
super(message)
|
|
57
|
-
this.name = 'DatabaseError'
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export class QueryError extends DatabaseError {
|
|
62
|
-
constructor(driverType: string, sql: string, originalError?: Error) {
|
|
63
|
-
super(`Query failed: ${sql}`, driverType, originalError)
|
|
64
|
-
this.name = 'QueryError'
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export class ConnectionError extends DatabaseError {
|
|
69
|
-
constructor(driverType: string, originalError?: Error) {
|
|
70
|
-
super(`Failed to connect to ${driverType}`, driverType, originalError)
|
|
71
|
-
this.name = 'ConnectionError'
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Generic driver options
|
|
76
|
-
export interface DriverOptions {
|
|
77
|
-
[key: string]: any
|
|
78
|
-
}
|