driftsql 1.0.12 → 1.0.14
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 +203 -118
- package/dist/index.d.mts +131 -45
- package/dist/index.d.ts +131 -45
- package/dist/index.mjs +463 -290
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -4,9 +4,314 @@ import ky from 'ky';
|
|
|
4
4
|
import { createClient as createClient$1 } from '@libsql/client';
|
|
5
5
|
import { createClient } from '@tursodatabase/serverless/compat';
|
|
6
6
|
import * as mysql from 'mysql2/promise';
|
|
7
|
+
import Database from 'better-sqlite3';
|
|
7
8
|
import fs from 'node:fs/promises';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
function hasTransactionSupport(driver) {
|
|
11
|
+
return "transaction" in driver && typeof driver.transaction === "function";
|
|
12
|
+
}
|
|
13
|
+
function hasPreparedStatementSupport(driver) {
|
|
14
|
+
return "prepare" in driver && typeof driver.prepare === "function";
|
|
15
|
+
}
|
|
16
|
+
class DatabaseError extends Error {
|
|
17
|
+
constructor(message, driverType, originalError) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.driverType = driverType;
|
|
20
|
+
this.originalError = originalError;
|
|
21
|
+
this.name = "DatabaseError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
class QueryError extends DatabaseError {
|
|
25
|
+
constructor(driverType, sql, originalError) {
|
|
26
|
+
super(`Query failed: ${sql}`, driverType, originalError);
|
|
27
|
+
this.name = "QueryError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
class ConnectionError extends DatabaseError {
|
|
31
|
+
constructor(driverType, originalError) {
|
|
32
|
+
super(`Failed to connect to ${driverType}`, driverType, originalError);
|
|
33
|
+
this.name = "ConnectionError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class PostgresDriver {
|
|
38
|
+
client;
|
|
39
|
+
constructor(config) {
|
|
40
|
+
try {
|
|
41
|
+
if (config.experimental?.http) {
|
|
42
|
+
this.client = new PostgresHTTPDriver(config.experimental.http);
|
|
43
|
+
} else {
|
|
44
|
+
this.client = postgres(config.connectionString || "");
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new ConnectionError("postgres", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async query(sql, params) {
|
|
51
|
+
try {
|
|
52
|
+
if (this.client instanceof PostgresHTTPDriver) {
|
|
53
|
+
return await this.client.query(sql, params);
|
|
54
|
+
}
|
|
55
|
+
const result = await this.client.unsafe(sql, params || []);
|
|
56
|
+
return {
|
|
57
|
+
rows: result,
|
|
58
|
+
rowCount: Array.isArray(result) ? result.length : 0,
|
|
59
|
+
command: void 0
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new QueryError("postgres", sql, error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async transaction(callback) {
|
|
66
|
+
if (this.client instanceof PostgresHTTPDriver) {
|
|
67
|
+
throw new Error("Transactions not supported with HTTP driver");
|
|
68
|
+
}
|
|
69
|
+
const result = await this.client.begin(async (sql) => {
|
|
70
|
+
const transactionDriver = new PostgresDriver({ connectionString: "" });
|
|
71
|
+
transactionDriver.client = sql;
|
|
72
|
+
return await callback(transactionDriver);
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
async close() {
|
|
77
|
+
try {
|
|
78
|
+
if (this.client instanceof PostgresHTTPDriver) {
|
|
79
|
+
return await this.client.close();
|
|
80
|
+
}
|
|
81
|
+
await this.client.end();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("Error closing Postgres client:", error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
class PostgresHTTPDriver {
|
|
88
|
+
httpClient;
|
|
89
|
+
constructor(config) {
|
|
90
|
+
this.httpClient = ky.create({
|
|
91
|
+
prefixUrl: config.url,
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: `Bearer ${config.apiKey || ""}`
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async query(sql, params) {
|
|
98
|
+
try {
|
|
99
|
+
const response = await this.httpClient.post("query", {
|
|
100
|
+
json: { query: sql, params }
|
|
101
|
+
}).json();
|
|
102
|
+
return {
|
|
103
|
+
rows: response.rows,
|
|
104
|
+
rowCount: response.rowCount,
|
|
105
|
+
command: void 0
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new QueryError("postgres-http", sql, error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async close() {
|
|
112
|
+
return Promise.resolve();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class LibSQLDriver {
|
|
117
|
+
client;
|
|
118
|
+
constructor(config) {
|
|
119
|
+
try {
|
|
120
|
+
this.client = config.useTursoServerlessDriver ? createClient({
|
|
121
|
+
url: config.url,
|
|
122
|
+
...config.authToken ? { authToken: config.authToken } : {}
|
|
123
|
+
}) : createClient$1({
|
|
124
|
+
url: config.url,
|
|
125
|
+
...config.authToken ? { authToken: config.authToken } : {}
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw new ConnectionError("libsql", error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async query(sql, params) {
|
|
132
|
+
try {
|
|
133
|
+
const result = await this.client.execute(sql, params);
|
|
134
|
+
return this.convertLibsqlResult(result);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new QueryError("libsql", sql, error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async transaction(callback) {
|
|
140
|
+
const transactionDriver = new LibSQLDriver({ url: "", authToken: "" });
|
|
141
|
+
transactionDriver.client = this.client;
|
|
142
|
+
return await callback(transactionDriver);
|
|
143
|
+
}
|
|
144
|
+
async close() {
|
|
145
|
+
try {
|
|
146
|
+
this.client.close();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error("Error closing LibSQL client:", error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
convertLibsqlResult(result) {
|
|
152
|
+
const rows = result.rows.map((row) => {
|
|
153
|
+
const obj = {};
|
|
154
|
+
result.columns.forEach((col, index) => {
|
|
155
|
+
obj[col] = row[index];
|
|
156
|
+
});
|
|
157
|
+
return obj;
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
rows,
|
|
161
|
+
rowCount: result.rowsAffected || rows.length,
|
|
162
|
+
command: void 0,
|
|
163
|
+
fields: result.columns.map((col) => ({ name: col, dataTypeID: 0 }))
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class MySQLDriver {
|
|
169
|
+
client;
|
|
170
|
+
constructor(config) {
|
|
171
|
+
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.");
|
|
172
|
+
try {
|
|
173
|
+
this.client = mysql.createConnection(config.connectionString);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new ConnectionError("mysql", error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async query(sql, params) {
|
|
179
|
+
try {
|
|
180
|
+
const [rows, fields] = await (await this.client).execute(sql, params || []);
|
|
181
|
+
const rowCount = Array.isArray(rows) ? rows.length : 0;
|
|
182
|
+
const normalizedFields = fields.map((field) => ({
|
|
183
|
+
name: field.name,
|
|
184
|
+
dataTypeID: field.columnType
|
|
185
|
+
}));
|
|
186
|
+
return {
|
|
187
|
+
rows,
|
|
188
|
+
rowCount,
|
|
189
|
+
command: void 0,
|
|
190
|
+
fields: normalizedFields
|
|
191
|
+
};
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new QueryError("mysql", sql, error);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async transaction(callback) {
|
|
197
|
+
const connection = await this.client;
|
|
198
|
+
try {
|
|
199
|
+
await connection.beginTransaction();
|
|
200
|
+
const transactionDriver = new MySQLDriver({ connectionString: "" });
|
|
201
|
+
transactionDriver.client = Promise.resolve(connection);
|
|
202
|
+
const result = await callback(transactionDriver);
|
|
203
|
+
await connection.commit();
|
|
204
|
+
return result;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
await connection.rollback();
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async close() {
|
|
211
|
+
try {
|
|
212
|
+
await (await this.client).end();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
consola.error("Error closing MySQL client:", error);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
class SqliteDriver {
|
|
220
|
+
client;
|
|
221
|
+
constructor(config) {
|
|
222
|
+
try {
|
|
223
|
+
this.client = new Database(config.filename, {
|
|
224
|
+
readonly: config.readonly || false,
|
|
225
|
+
fileMustExist: config.readonly || false
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw new ConnectionError("sqlite", error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async query(sql, params) {
|
|
232
|
+
try {
|
|
233
|
+
const stmt = this.client.prepare(sql);
|
|
234
|
+
const rows = stmt.all(params || []);
|
|
235
|
+
const fields = rows.length > 0 && typeof rows[0] === "object" && rows[0] !== null ? Object.keys(rows[0]).map((name) => ({ name, dataTypeID: 0 })) : [];
|
|
236
|
+
return {
|
|
237
|
+
rows,
|
|
238
|
+
rowCount: rows.length,
|
|
239
|
+
command: void 0,
|
|
240
|
+
fields
|
|
241
|
+
};
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw new QueryError("sqlite", sql, error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async transaction(callback) {
|
|
247
|
+
const transaction = this.client.transaction(() => {
|
|
248
|
+
const transactionDriver = new SqliteDriver({ filename: "" });
|
|
249
|
+
transactionDriver.client = this.client;
|
|
250
|
+
return callback(transactionDriver);
|
|
251
|
+
});
|
|
252
|
+
return await transaction();
|
|
253
|
+
}
|
|
254
|
+
async prepare(sql) {
|
|
255
|
+
return new SqlitePreparedStatement(this.client.prepare(sql));
|
|
256
|
+
}
|
|
257
|
+
async close() {
|
|
258
|
+
try {
|
|
259
|
+
this.client.close();
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error("Error closing SQLite client:", error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// SQLite-specific methods
|
|
265
|
+
exec(sql) {
|
|
266
|
+
this.client.exec(sql);
|
|
267
|
+
}
|
|
268
|
+
backup(filename) {
|
|
269
|
+
return new Promise((resolve, reject) => {
|
|
270
|
+
try {
|
|
271
|
+
this.client.backup(filename);
|
|
272
|
+
resolve();
|
|
273
|
+
} catch (error) {
|
|
274
|
+
reject(error);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
pragma(pragma) {
|
|
279
|
+
return this.client.pragma(pragma);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
class SqlitePreparedStatement {
|
|
283
|
+
constructor(stmt) {
|
|
284
|
+
this.stmt = stmt;
|
|
285
|
+
}
|
|
286
|
+
async execute(params) {
|
|
287
|
+
try {
|
|
288
|
+
const rows = this.stmt.all(params || []);
|
|
289
|
+
const fields = rows.length > 0 && typeof rows[0] === "object" && rows[0] !== null ? Object.keys(rows[0]).map((name) => ({ name, dataTypeID: 0 })) : [];
|
|
290
|
+
return {
|
|
291
|
+
rows,
|
|
292
|
+
rowCount: rows.length,
|
|
293
|
+
command: void 0,
|
|
294
|
+
fields
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw new QueryError("sqlite", "prepared statement", error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async finalize() {
|
|
301
|
+
return Promise.resolve();
|
|
302
|
+
}
|
|
303
|
+
// SQLite-specific methods for prepared statements
|
|
304
|
+
run(params) {
|
|
305
|
+
return this.stmt.run(params || []);
|
|
306
|
+
}
|
|
307
|
+
get(params) {
|
|
308
|
+
return this.stmt.get(params || []);
|
|
309
|
+
}
|
|
310
|
+
all(params) {
|
|
311
|
+
return this.stmt.all(params || []);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
10
315
|
const withTimeout = (promise, timeoutMs = 3e4) => {
|
|
11
316
|
return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error(`Query timeout after ${timeoutMs}ms`)), timeoutMs))]);
|
|
12
317
|
};
|
|
@@ -108,24 +413,23 @@ const mapDatabaseTypeToTypeScript = (dataType, isNullable = false, driverType =
|
|
|
108
413
|
}
|
|
109
414
|
}
|
|
110
415
|
};
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
console.log(`
|
|
122
|
-
const
|
|
416
|
+
const getDriverType = (driver) => {
|
|
417
|
+
if (driver instanceof PostgresDriver) return "postgres";
|
|
418
|
+
if (driver instanceof LibSQLDriver) return "libsql";
|
|
419
|
+
if (driver instanceof MySQLDriver) return "mysql";
|
|
420
|
+
if (driver instanceof SqliteDriver) return "sqlite";
|
|
421
|
+
return "unknown";
|
|
422
|
+
};
|
|
423
|
+
const inspectDB = async (options) => {
|
|
424
|
+
const { driver, outputFile = "db-types.ts" } = options;
|
|
425
|
+
const driverType = getDriverType(driver);
|
|
426
|
+
console.log(`Inspecting database using ${driverType} driver`);
|
|
427
|
+
const client = new SQLClient({ driver });
|
|
123
428
|
let generatedTypes = "";
|
|
124
|
-
const client = new DriftSQLClient({ drivers });
|
|
125
429
|
try {
|
|
126
430
|
let tablesQuery;
|
|
127
431
|
let tableSchemaFilter;
|
|
128
|
-
if (
|
|
432
|
+
if (driverType === "mysql") {
|
|
129
433
|
const dbResult = await withTimeout(
|
|
130
434
|
retryQuery(() => client.query("SELECT DATABASE() as `database`", [])),
|
|
131
435
|
1e4
|
|
@@ -136,24 +440,26 @@ const inspectDB = async (drivers) => {
|
|
|
136
440
|
}
|
|
137
441
|
console.log(`Using MySQL database: ${currentDatabase}`);
|
|
138
442
|
tablesQuery = `SELECT TABLE_NAME as table_name
|
|
139
|
-
FROM information_schema.tables
|
|
140
|
-
WHERE TABLE_SCHEMA = ?
|
|
443
|
+
FROM information_schema.tables
|
|
444
|
+
WHERE TABLE_SCHEMA = ?
|
|
141
445
|
AND TABLE_TYPE = 'BASE TABLE'
|
|
142
446
|
ORDER BY TABLE_NAME`;
|
|
143
447
|
tableSchemaFilter = currentDatabase;
|
|
144
|
-
} else if (
|
|
145
|
-
tablesQuery = `SELECT table_name
|
|
146
|
-
FROM information_schema.tables
|
|
147
|
-
WHERE table_schema = $1
|
|
448
|
+
} else if (driverType === "postgres") {
|
|
449
|
+
tablesQuery = `SELECT table_name
|
|
450
|
+
FROM information_schema.tables
|
|
451
|
+
WHERE table_schema = $1
|
|
148
452
|
AND table_type = 'BASE TABLE'
|
|
149
453
|
ORDER BY table_name`;
|
|
150
454
|
tableSchemaFilter = "public";
|
|
151
|
-
} else {
|
|
152
|
-
tablesQuery = `SELECT name as table_name
|
|
153
|
-
FROM sqlite_master
|
|
154
|
-
WHERE type = 'table'
|
|
455
|
+
} else if (driverType === "libsql" || driverType === "sqlite") {
|
|
456
|
+
tablesQuery = `SELECT name as table_name
|
|
457
|
+
FROM sqlite_master
|
|
458
|
+
WHERE type = 'table'
|
|
155
459
|
ORDER BY name`;
|
|
156
460
|
tableSchemaFilter = void 0;
|
|
461
|
+
} else {
|
|
462
|
+
throw new Error(`Unsupported driver type: ${driverType}`);
|
|
157
463
|
}
|
|
158
464
|
const tables = await withTimeout(
|
|
159
465
|
retryQuery(() => client.query(tablesQuery, tableSchemaFilter ? [tableSchemaFilter] : [])),
|
|
@@ -169,37 +475,37 @@ const inspectDB = async (drivers) => {
|
|
|
169
475
|
try {
|
|
170
476
|
let columnsQuery;
|
|
171
477
|
let queryParams;
|
|
172
|
-
if (
|
|
478
|
+
if (driverType === "mysql") {
|
|
173
479
|
columnsQuery = `
|
|
174
|
-
SELECT
|
|
175
|
-
COLUMN_NAME as column_name,
|
|
176
|
-
DATA_TYPE as data_type,
|
|
480
|
+
SELECT
|
|
481
|
+
COLUMN_NAME as column_name,
|
|
482
|
+
DATA_TYPE as data_type,
|
|
177
483
|
IS_NULLABLE as is_nullable,
|
|
178
484
|
COLUMN_DEFAULT as column_default
|
|
179
|
-
FROM information_schema.columns
|
|
180
|
-
WHERE TABLE_NAME = ?
|
|
485
|
+
FROM information_schema.columns
|
|
486
|
+
WHERE TABLE_NAME = ?
|
|
181
487
|
AND TABLE_SCHEMA = ?
|
|
182
488
|
ORDER BY ORDINAL_POSITION
|
|
183
489
|
`;
|
|
184
490
|
queryParams = [tableName, tableSchemaFilter];
|
|
185
|
-
} else if (
|
|
491
|
+
} else if (driverType === "postgres") {
|
|
186
492
|
columnsQuery = `
|
|
187
|
-
SELECT
|
|
188
|
-
column_name,
|
|
189
|
-
data_type,
|
|
493
|
+
SELECT
|
|
494
|
+
column_name,
|
|
495
|
+
data_type,
|
|
190
496
|
is_nullable,
|
|
191
497
|
column_default
|
|
192
|
-
FROM information_schema.columns
|
|
193
|
-
WHERE table_name = $1
|
|
498
|
+
FROM information_schema.columns
|
|
499
|
+
WHERE table_name = $1
|
|
194
500
|
AND table_schema = $2
|
|
195
501
|
ORDER BY ordinal_position
|
|
196
502
|
`;
|
|
197
503
|
queryParams = [tableName, tableSchemaFilter];
|
|
198
504
|
} else {
|
|
199
505
|
columnsQuery = `
|
|
200
|
-
SELECT
|
|
201
|
-
name as column_name,
|
|
202
|
-
type as data_type,
|
|
506
|
+
SELECT
|
|
507
|
+
name as column_name,
|
|
508
|
+
type as data_type,
|
|
203
509
|
CASE WHEN "notnull" = 0 THEN 'YES' ELSE 'NO' END as is_nullable,
|
|
204
510
|
dflt_value as column_default
|
|
205
511
|
FROM pragma_table_info(?)
|
|
@@ -212,7 +518,6 @@ const inspectDB = async (drivers) => {
|
|
|
212
518
|
() => client.query(columnsQuery, queryParams)
|
|
213
519
|
),
|
|
214
520
|
15e3
|
|
215
|
-
// Shorter timeout for individual table queries
|
|
216
521
|
);
|
|
217
522
|
if (columns.rows.length === 0) {
|
|
218
523
|
console.log(`No columns found for table: ${tableName}`);
|
|
@@ -228,7 +533,7 @@ const inspectDB = async (drivers) => {
|
|
|
228
533
|
generatedTypes += `export interface ${tableName.charAt(0).toUpperCase() + tableName.slice(1)} {
|
|
229
534
|
`;
|
|
230
535
|
for (const col of uniqueColumns.values()) {
|
|
231
|
-
const tsType = mapDatabaseTypeToTypeScript(col.data_type, col.is_nullable === "YES",
|
|
536
|
+
const tsType = mapDatabaseTypeToTypeScript(col.data_type, col.is_nullable === "YES", driverType);
|
|
232
537
|
generatedTypes += ` ${col.column_name}: ${tsType};
|
|
233
538
|
`;
|
|
234
539
|
}
|
|
@@ -241,274 +546,124 @@ const inspectDB = async (drivers) => {
|
|
|
241
546
|
}
|
|
242
547
|
generatedTypes += "export interface Database {\n";
|
|
243
548
|
for (const table of tables.rows) {
|
|
244
|
-
const
|
|
245
|
-
generatedTypes += ` ${
|
|
549
|
+
const interfaceName = table.table_name.charAt(0).toUpperCase() + table.table_name.slice(1);
|
|
550
|
+
generatedTypes += ` ${table.table_name}: ${interfaceName};
|
|
246
551
|
`;
|
|
247
552
|
}
|
|
248
553
|
generatedTypes += "}\n\n";
|
|
249
|
-
await fs.writeFile(
|
|
250
|
-
console.log(
|
|
554
|
+
await fs.writeFile(outputFile, generatedTypes, "utf8");
|
|
555
|
+
console.log(`TypeScript types written to ${outputFile}`);
|
|
251
556
|
console.log(`Successfully processed ${processedTables} tables`);
|
|
252
557
|
} catch (error) {
|
|
253
558
|
console.error("Fatal error during database inspection:", error);
|
|
254
|
-
|
|
559
|
+
throw error;
|
|
560
|
+
} finally {
|
|
561
|
+
await client.close();
|
|
255
562
|
}
|
|
256
|
-
|
|
563
|
+
};
|
|
564
|
+
const inspectPostgres = async (config, outputFile) => {
|
|
565
|
+
const driver = new PostgresDriver(config);
|
|
566
|
+
return inspectDB({ driver, outputFile });
|
|
567
|
+
};
|
|
568
|
+
const inspectLibSQL = async (config, outputFile) => {
|
|
569
|
+
const driver = new LibSQLDriver(config);
|
|
570
|
+
return inspectDB({ driver, outputFile });
|
|
571
|
+
};
|
|
572
|
+
const inspectMySQL = async (config, outputFile) => {
|
|
573
|
+
const driver = new MySQLDriver(config);
|
|
574
|
+
return inspectDB({ driver, outputFile });
|
|
575
|
+
};
|
|
576
|
+
const inspectSQLite = async (config, outputFile) => {
|
|
577
|
+
const driver = new SqliteDriver(config);
|
|
578
|
+
return inspectDB({ driver, outputFile });
|
|
257
579
|
};
|
|
258
580
|
|
|
259
|
-
class
|
|
581
|
+
class SQLClient {
|
|
582
|
+
primaryDriver;
|
|
583
|
+
fallbackDrivers;
|
|
260
584
|
constructor(options) {
|
|
261
|
-
this.
|
|
262
|
-
|
|
263
|
-
this.client = new PostgresHTTPDriver(this.options.options.experimental);
|
|
264
|
-
} else {
|
|
265
|
-
this.client = postgres(this.options.connectionString || "");
|
|
266
|
-
}
|
|
585
|
+
this.primaryDriver = options.driver;
|
|
586
|
+
this.fallbackDrivers = options.fallbackDrivers || [];
|
|
267
587
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
command: void 0
|
|
279
|
-
};
|
|
280
|
-
} catch (error) {
|
|
281
|
-
console.error("Postgres query error:", error);
|
|
282
|
-
throw error;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
async close() {
|
|
286
|
-
try {
|
|
287
|
-
if (this.client instanceof PostgresHTTPDriver) {
|
|
288
|
-
return await this.client.close();
|
|
289
|
-
}
|
|
290
|
-
await this.client.end();
|
|
291
|
-
} catch (error) {
|
|
292
|
-
console.error("Error closing Postgres client:", error);
|
|
293
|
-
throw error;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
class PostgresHTTPDriver {
|
|
298
|
-
httpClient;
|
|
299
|
-
constructor(options) {
|
|
300
|
-
if (!options || !options.http || !options.http.url) {
|
|
301
|
-
throw new Error("Postgres HTTP driver requires a valid URL in options.http");
|
|
302
|
-
}
|
|
303
|
-
this.httpClient = ky.create({
|
|
304
|
-
prefixUrl: options.http.url,
|
|
305
|
-
headers: {
|
|
306
|
-
Authorization: `Bearer ${options.http.apiKey || ""}`
|
|
588
|
+
async query(sql, params) {
|
|
589
|
+
const drivers = [this.primaryDriver, ...this.fallbackDrivers];
|
|
590
|
+
let lastError;
|
|
591
|
+
for (const driver of drivers) {
|
|
592
|
+
try {
|
|
593
|
+
return await driver.query(sql, params);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
lastError = error;
|
|
596
|
+
consola.warn(`Query failed with ${driver.constructor.name}:`, error);
|
|
597
|
+
continue;
|
|
307
598
|
}
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
async query(query, params) {
|
|
311
|
-
try {
|
|
312
|
-
const response = await this.httpClient.post("query", {
|
|
313
|
-
json: {
|
|
314
|
-
query,
|
|
315
|
-
params
|
|
316
|
-
}
|
|
317
|
-
}).json();
|
|
318
|
-
return {
|
|
319
|
-
rows: response.rows,
|
|
320
|
-
rowCount: response.rowCount,
|
|
321
|
-
command: void 0
|
|
322
|
-
};
|
|
323
|
-
} catch (error) {
|
|
324
|
-
console.error("Postgres HTTP query error:", error);
|
|
325
|
-
throw error;
|
|
326
599
|
}
|
|
600
|
+
throw lastError || new DatabaseError("All drivers failed to execute query", "unknown");
|
|
327
601
|
}
|
|
328
|
-
async
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
this.client = this.options?.options?.experimental?.useTursoServerlessDriver ? createClient({
|
|
337
|
-
url: this.options.url,
|
|
338
|
-
...this.options.authToken ? { authToken: this.options.authToken } : {}
|
|
339
|
-
}) : createClient$1({
|
|
340
|
-
url: this.options.url,
|
|
341
|
-
...this.options.authToken ? { authToken: this.options.authToken } : {}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
client;
|
|
345
|
-
convertLibsqlResult(result) {
|
|
346
|
-
const rows = result.rows.map((row) => {
|
|
347
|
-
const obj = {};
|
|
348
|
-
result.columns.forEach((col, index) => {
|
|
349
|
-
obj[col] = row[index];
|
|
602
|
+
async transaction(callback) {
|
|
603
|
+
if (!hasTransactionSupport(this.primaryDriver)) {
|
|
604
|
+
throw new DatabaseError("Primary driver does not support transactions", this.primaryDriver.constructor.name);
|
|
605
|
+
}
|
|
606
|
+
return await this.primaryDriver.transaction(async (transactionDriver) => {
|
|
607
|
+
const transactionClient = new SQLClient({
|
|
608
|
+
driver: transactionDriver,
|
|
609
|
+
fallbackDrivers: []
|
|
350
610
|
});
|
|
351
|
-
return
|
|
611
|
+
return await callback(transactionClient);
|
|
352
612
|
});
|
|
353
|
-
return {
|
|
354
|
-
rows,
|
|
355
|
-
rowCount: result.rowsAffected || rows.length,
|
|
356
|
-
command: void 0,
|
|
357
|
-
fields: result.columns.map((col) => ({ name: col, dataTypeID: 0 }))
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
async query(query, params) {
|
|
361
|
-
try {
|
|
362
|
-
const result = await this.client.execute(query, params);
|
|
363
|
-
return this.convertLibsqlResult(result);
|
|
364
|
-
} catch (error) {
|
|
365
|
-
console.error("LibSQL query error:", error);
|
|
366
|
-
throw error;
|
|
367
|
-
}
|
|
368
613
|
}
|
|
369
|
-
async
|
|
370
|
-
|
|
371
|
-
this.
|
|
372
|
-
} catch (error) {
|
|
373
|
-
console.error("Error closing LibSQL client:", error);
|
|
614
|
+
async prepare(sql) {
|
|
615
|
+
if (!hasPreparedStatementSupport(this.primaryDriver)) {
|
|
616
|
+
throw new DatabaseError("Primary driver does not support prepared statements", this.primaryDriver.constructor.name);
|
|
374
617
|
}
|
|
618
|
+
return await this.primaryDriver.prepare(sql);
|
|
375
619
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
class MySQLDriver {
|
|
379
|
-
constructor(options) {
|
|
380
|
-
this.options = options;
|
|
381
|
-
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.");
|
|
382
|
-
this.client = mysql.createConnection(this.options.connectionString);
|
|
383
|
-
}
|
|
384
|
-
client;
|
|
385
|
-
async query(query, params) {
|
|
386
|
-
try {
|
|
387
|
-
const [rows, fields] = await (await this.client).execute(query, params || []);
|
|
388
|
-
const rowCount = Array.isArray(rows) ? rows.length : 0;
|
|
389
|
-
return {
|
|
390
|
-
rows,
|
|
391
|
-
rowCount,
|
|
392
|
-
command: void 0,
|
|
393
|
-
fields: fields.map((field) => ({ name: field.name, dataTypeID: field.columnType }))
|
|
394
|
-
};
|
|
395
|
-
} catch (error) {
|
|
396
|
-
consola.error("MySQL query error:", error);
|
|
397
|
-
throw error;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
async close() {
|
|
401
|
-
try {
|
|
402
|
-
await (await this.client).end();
|
|
403
|
-
} catch (error) {
|
|
404
|
-
consola.error("Error closing MySQL client:", error);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
class DriftSQLClient {
|
|
410
|
-
postgres;
|
|
411
|
-
libsql;
|
|
412
|
-
mysql;
|
|
413
|
-
drivers;
|
|
414
|
-
constructor(options) {
|
|
415
|
-
this.postgres = options.drivers?.postgres ? new PostgresDriver({ connectionString: options.drivers.postgres.connectionString }) : void 0;
|
|
416
|
-
this.libsql = options.drivers?.libsql ? new LibSQLDriver(options.drivers.libsql) : void 0;
|
|
417
|
-
this.mysql = options.drivers?.mysql ? new MySQLDriver(options.drivers.mysql) : void 0;
|
|
418
|
-
this.drivers = options.drivers || {};
|
|
419
|
-
}
|
|
420
|
-
inspect = async () => {
|
|
421
|
-
return inspectDB(this.drivers);
|
|
422
|
-
};
|
|
423
|
-
async query(query, args) {
|
|
424
|
-
if (this.postgres) {
|
|
425
|
-
try {
|
|
426
|
-
const result = await this.postgres.query(query, args || []);
|
|
427
|
-
return {
|
|
428
|
-
rows: result.rows,
|
|
429
|
-
rowCount: result.rowCount || 0,
|
|
430
|
-
command: result.command,
|
|
431
|
-
fields: result.fields?.map((field) => ({ name: field.name, dataTypeID: field.dataTypeID })) || []
|
|
432
|
-
};
|
|
433
|
-
} catch (error2) {
|
|
434
|
-
consola.error("Failed to execute query with PostgreSQL:", error2);
|
|
435
|
-
throw error2;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (this.mysql) {
|
|
439
|
-
try {
|
|
440
|
-
const result = await this.mysql.query(query, args || []);
|
|
441
|
-
return {
|
|
442
|
-
rows: result.rows,
|
|
443
|
-
rowCount: result.rowCount || 0,
|
|
444
|
-
command: result.command,
|
|
445
|
-
fields: result.fields?.map((field) => ({ name: field.name, dataTypeID: field.dataTypeID })) || []
|
|
446
|
-
};
|
|
447
|
-
} catch (error2) {
|
|
448
|
-
consola.error("Failed to execute query with MySQL:", error2);
|
|
449
|
-
throw error2;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
if (this.libsql) {
|
|
453
|
-
try {
|
|
454
|
-
const result = await this.libsql.query(query, args || []);
|
|
455
|
-
return {
|
|
456
|
-
rows: result.rows,
|
|
457
|
-
rowCount: result.rowCount || 0,
|
|
458
|
-
command: result.command,
|
|
459
|
-
fields: void 0
|
|
460
|
-
};
|
|
461
|
-
} catch (error2) {
|
|
462
|
-
consola.error("Failed to execute query with LibSQL:", error2);
|
|
463
|
-
throw error2;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
const error = new Error("No database driver is configured or all drivers failed to execute the query");
|
|
467
|
-
consola.error(error);
|
|
468
|
-
throw error;
|
|
469
|
-
}
|
|
620
|
+
// Helper methods for common database operations
|
|
470
621
|
async findFirst(table, where) {
|
|
471
622
|
const tableName = String(table);
|
|
472
623
|
const whereEntries = Object.entries(where || {});
|
|
473
|
-
let
|
|
474
|
-
let
|
|
624
|
+
let sql = `SELECT * FROM ${tableName}`;
|
|
625
|
+
let params = [];
|
|
475
626
|
if (whereEntries.length > 0) {
|
|
476
|
-
const whereClause = whereEntries.map((
|
|
477
|
-
|
|
478
|
-
|
|
627
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(" AND ");
|
|
628
|
+
sql += ` WHERE ${whereClause}`;
|
|
629
|
+
params = whereEntries.map(([, value]) => value);
|
|
479
630
|
}
|
|
480
|
-
|
|
481
|
-
const result = await this.query(
|
|
631
|
+
sql += " LIMIT 1";
|
|
632
|
+
const result = await this.query(sql, params);
|
|
482
633
|
return result.rows[0] || null;
|
|
483
634
|
}
|
|
484
635
|
async findMany(table, options) {
|
|
485
636
|
const tableName = String(table);
|
|
486
|
-
const { where, limit } = options || {};
|
|
637
|
+
const { where, limit, offset } = options || {};
|
|
487
638
|
const whereEntries = Object.entries(where || {});
|
|
488
|
-
let
|
|
489
|
-
let
|
|
639
|
+
let sql = `SELECT * FROM ${tableName}`;
|
|
640
|
+
let params = [];
|
|
490
641
|
if (whereEntries.length > 0) {
|
|
491
|
-
const whereClause = whereEntries.map((
|
|
492
|
-
|
|
493
|
-
|
|
642
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(" AND ");
|
|
643
|
+
sql += ` WHERE ${whereClause}`;
|
|
644
|
+
params = whereEntries.map(([, value]) => value);
|
|
494
645
|
}
|
|
495
646
|
if (typeof limit === "number" && limit > 0) {
|
|
496
|
-
|
|
497
|
-
|
|
647
|
+
sql += ` LIMIT $${params.length + 1}`;
|
|
648
|
+
params.push(limit);
|
|
649
|
+
}
|
|
650
|
+
if (typeof offset === "number" && offset > 0) {
|
|
651
|
+
sql += ` OFFSET $${params.length + 1}`;
|
|
652
|
+
params.push(offset);
|
|
498
653
|
}
|
|
499
|
-
const result = await this.query(
|
|
654
|
+
const result = await this.query(sql, params);
|
|
500
655
|
return result.rows;
|
|
501
656
|
}
|
|
502
657
|
async insert(table, data) {
|
|
503
658
|
const tableName = String(table);
|
|
504
659
|
const keys = Object.keys(data);
|
|
505
|
-
const values = Object.values(data)
|
|
660
|
+
const values = Object.values(data);
|
|
506
661
|
if (keys.length === 0) {
|
|
507
662
|
throw new Error("No data provided for insert");
|
|
508
663
|
}
|
|
509
664
|
const placeholders = keys.map((_, index) => `$${index + 1}`).join(", ");
|
|
510
|
-
const
|
|
511
|
-
const result = await this.query(
|
|
665
|
+
const sql = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders}) RETURNING *`;
|
|
666
|
+
const result = await this.query(sql, values);
|
|
512
667
|
if (!result.rows[0]) {
|
|
513
668
|
throw new Error("Insert failed: No data returned");
|
|
514
669
|
}
|
|
@@ -524,11 +679,11 @@ class DriftSQLClient {
|
|
|
524
679
|
if (whereEntries.length === 0) {
|
|
525
680
|
throw new Error("No conditions provided for update");
|
|
526
681
|
}
|
|
527
|
-
const setClause = setEntries.map((
|
|
528
|
-
const whereClause = whereEntries.map((
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
const result = await this.query(
|
|
682
|
+
const setClause = setEntries.map((_, index) => `${setEntries[index]?.[0]} = $${index + 1}`).join(", ");
|
|
683
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${setEntries.length + index + 1}`).join(" AND ");
|
|
684
|
+
const sql = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause} RETURNING *`;
|
|
685
|
+
const params = [...setEntries.map(([, value]) => value), ...whereEntries.map(([, value]) => value)];
|
|
686
|
+
const result = await this.query(sql, params);
|
|
532
687
|
return result.rows[0] || null;
|
|
533
688
|
}
|
|
534
689
|
async delete(table, where) {
|
|
@@ -537,26 +692,44 @@ class DriftSQLClient {
|
|
|
537
692
|
if (whereEntries.length === 0) {
|
|
538
693
|
throw new Error("No conditions provided for delete");
|
|
539
694
|
}
|
|
540
|
-
const whereClause = whereEntries.map((
|
|
541
|
-
const
|
|
542
|
-
const
|
|
543
|
-
const result = await this.query(
|
|
544
|
-
return
|
|
695
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(" AND ");
|
|
696
|
+
const sql = `DELETE FROM ${tableName} WHERE ${whereClause}`;
|
|
697
|
+
const params = whereEntries.map(([, value]) => value);
|
|
698
|
+
const result = await this.query(sql, params);
|
|
699
|
+
return result.rowCount || 0;
|
|
545
700
|
}
|
|
546
|
-
|
|
547
|
-
|
|
701
|
+
// Get the primary driver (useful for driver-specific operations)
|
|
702
|
+
getDriver() {
|
|
703
|
+
return this.primaryDriver;
|
|
704
|
+
}
|
|
705
|
+
// Check driver capabilities
|
|
706
|
+
supportsTransactions() {
|
|
707
|
+
return hasTransactionSupport(this.primaryDriver);
|
|
708
|
+
}
|
|
709
|
+
supportsPreparedStatements() {
|
|
710
|
+
return hasPreparedStatementSupport(this.primaryDriver);
|
|
548
711
|
}
|
|
549
712
|
async close() {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
if (this.libsql) {
|
|
554
|
-
this.libsql.close();
|
|
555
|
-
}
|
|
556
|
-
if (this.mysql) {
|
|
557
|
-
await this.mysql.close();
|
|
558
|
-
}
|
|
713
|
+
const drivers = [this.primaryDriver, ...this.fallbackDrivers];
|
|
714
|
+
await Promise.all(drivers.map((driver) => driver.close().catch((err) => consola.warn(`Error closing ${driver.constructor.name}:`, err))));
|
|
559
715
|
}
|
|
560
716
|
}
|
|
717
|
+
function createPostgresClient(config) {
|
|
718
|
+
const { PostgresDriver: PostgresDriver2 } = require("./drivers/postgres");
|
|
719
|
+
return new SQLClient({ driver: new PostgresDriver2(config) });
|
|
720
|
+
}
|
|
721
|
+
function createLibSQLClient(config) {
|
|
722
|
+
const { LibSQLDriver: LibSQLDriver2 } = require("./drivers/libsql");
|
|
723
|
+
return new SQLClient({ driver: new LibSQLDriver2(config) });
|
|
724
|
+
}
|
|
725
|
+
function createMySQLClient(config) {
|
|
726
|
+
const { MySQLDriver: MySQLDriver2 } = require("./drivers/mysql");
|
|
727
|
+
return new SQLClient({ driver: new MySQLDriver2(config) });
|
|
728
|
+
}
|
|
729
|
+
function createSqliteClient(config) {
|
|
730
|
+
const { SqliteDriver: SqliteDriver2 } = require("./drivers/sqlite");
|
|
731
|
+
return new SQLClient({ driver: new SqliteDriver2(config) });
|
|
732
|
+
}
|
|
733
|
+
const DriftSQLClient = SQLClient;
|
|
561
734
|
|
|
562
|
-
export { DriftSQLClient, inspectDB };
|
|
735
|
+
export { DriftSQLClient, LibSQLDriver, MySQLDriver, PostgresDriver, SQLClient, SqliteDriver, createLibSQLClient, createMySQLClient, createPostgresClient, createSqliteClient, inspectDB, inspectLibSQL, inspectMySQL, inspectPostgres, inspectSQLite };
|