driftsql 1.0.12 → 1.0.13
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 +105 -46
- package/dist/index.d.ts +105 -46
- package/dist/index.mjs +308 -402
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -4,284 +4,74 @@ 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
|
|
7
|
+
import Database from 'better-sqlite3';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
22
|
-
console.warn(`Query attempt ${attempt} failed, retrying in ${delay}ms...`, error);
|
|
23
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
24
|
-
}
|
|
9
|
+
function hasTransactionSupport(driver) {
|
|
10
|
+
return "transaction" in driver && typeof driver.transaction === "function";
|
|
11
|
+
}
|
|
12
|
+
function hasPreparedStatementSupport(driver) {
|
|
13
|
+
return "prepare" in driver && typeof driver.prepare === "function";
|
|
14
|
+
}
|
|
15
|
+
class DatabaseError extends Error {
|
|
16
|
+
constructor(message, driverType, originalError) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.driverType = driverType;
|
|
19
|
+
this.originalError = originalError;
|
|
20
|
+
this.name = "DatabaseError";
|
|
25
21
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
switch (lowerType) {
|
|
32
|
-
case "uuid": {
|
|
33
|
-
return `string${nullable}`;
|
|
34
|
-
}
|
|
35
|
-
case "character varying":
|
|
36
|
-
case "varchar":
|
|
37
|
-
case "text":
|
|
38
|
-
case "char":
|
|
39
|
-
case "character":
|
|
40
|
-
case "longtext":
|
|
41
|
-
case "mediumtext":
|
|
42
|
-
case "tinytext": {
|
|
43
|
-
return `string${nullable}`;
|
|
44
|
-
}
|
|
45
|
-
case "integer":
|
|
46
|
-
case "int":
|
|
47
|
-
case "int4":
|
|
48
|
-
case "smallint":
|
|
49
|
-
case "int2":
|
|
50
|
-
case "bigint":
|
|
51
|
-
case "int8":
|
|
52
|
-
case "serial":
|
|
53
|
-
case "bigserial":
|
|
54
|
-
case "numeric":
|
|
55
|
-
case "decimal":
|
|
56
|
-
case "real":
|
|
57
|
-
case "float4":
|
|
58
|
-
case "double precision":
|
|
59
|
-
case "float8":
|
|
60
|
-
case "tinyint":
|
|
61
|
-
case "mediumint":
|
|
62
|
-
case "float":
|
|
63
|
-
case "double": {
|
|
64
|
-
return `number${nullable}`;
|
|
65
|
-
}
|
|
66
|
-
case "boolean":
|
|
67
|
-
case "bool":
|
|
68
|
-
case "bit": {
|
|
69
|
-
return `boolean${nullable}`;
|
|
70
|
-
}
|
|
71
|
-
case "timestamp":
|
|
72
|
-
case "timestamp with time zone":
|
|
73
|
-
case "timestamp without time zone":
|
|
74
|
-
case "timestamptz":
|
|
75
|
-
case "date":
|
|
76
|
-
case "time":
|
|
77
|
-
case "time with time zone":
|
|
78
|
-
case "time without time zone":
|
|
79
|
-
case "timetz":
|
|
80
|
-
case "interval":
|
|
81
|
-
case "datetime":
|
|
82
|
-
case "year": {
|
|
83
|
-
return `Date${nullable}`;
|
|
84
|
-
}
|
|
85
|
-
case "json":
|
|
86
|
-
case "jsonb": {
|
|
87
|
-
return `any${nullable}`;
|
|
88
|
-
}
|
|
89
|
-
case "array": {
|
|
90
|
-
return `any[]${nullable}`;
|
|
91
|
-
}
|
|
92
|
-
case "bytea":
|
|
93
|
-
case "binary":
|
|
94
|
-
case "varbinary":
|
|
95
|
-
case "blob":
|
|
96
|
-
case "longblob":
|
|
97
|
-
case "mediumblob":
|
|
98
|
-
case "tinyblob": {
|
|
99
|
-
return `Buffer${nullable}`;
|
|
100
|
-
}
|
|
101
|
-
case "enum":
|
|
102
|
-
case "set": {
|
|
103
|
-
return `string${nullable}`;
|
|
104
|
-
}
|
|
105
|
-
default: {
|
|
106
|
-
console.warn(`Unknown ${driverType} type: ${dataType}, defaulting to 'any'`);
|
|
107
|
-
return `any${nullable}`;
|
|
108
|
-
}
|
|
22
|
+
}
|
|
23
|
+
class QueryError extends DatabaseError {
|
|
24
|
+
constructor(driverType, sql, originalError) {
|
|
25
|
+
super(`Query failed: ${sql}`, driverType, originalError);
|
|
26
|
+
this.name = "QueryError";
|
|
109
27
|
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const supportedConfiguredDrivers = configuredDrivers.filter((driver) => supportedDrivers.includes(driver));
|
|
118
|
-
if (supportedConfiguredDrivers.length === 0) {
|
|
119
|
-
throw new Error(`No supported drivers found. Configured: ${configuredDrivers.join(", ")}. Supported: ${supportedDrivers.join(", ")}`);
|
|
120
|
-
}
|
|
121
|
-
console.log(`Found supported drivers: ${supportedConfiguredDrivers.join(", ")}`);
|
|
122
|
-
const activeDriver = supportedConfiguredDrivers[0];
|
|
123
|
-
let generatedTypes = "";
|
|
124
|
-
const client = new DriftSQLClient({ drivers });
|
|
125
|
-
try {
|
|
126
|
-
let tablesQuery;
|
|
127
|
-
let tableSchemaFilter;
|
|
128
|
-
if (activeDriver === "mysql") {
|
|
129
|
-
const dbResult = await withTimeout(
|
|
130
|
-
retryQuery(() => client.query("SELECT DATABASE() as `database`", [])),
|
|
131
|
-
1e4
|
|
132
|
-
);
|
|
133
|
-
const currentDatabase = dbResult.rows[0]?.database;
|
|
134
|
-
if (!currentDatabase) {
|
|
135
|
-
throw new Error("Could not determine current MySQL database name");
|
|
136
|
-
}
|
|
137
|
-
console.log(`Using MySQL database: ${currentDatabase}`);
|
|
138
|
-
tablesQuery = `SELECT TABLE_NAME as table_name
|
|
139
|
-
FROM information_schema.tables
|
|
140
|
-
WHERE TABLE_SCHEMA = ?
|
|
141
|
-
AND TABLE_TYPE = 'BASE TABLE'
|
|
142
|
-
ORDER BY TABLE_NAME`;
|
|
143
|
-
tableSchemaFilter = currentDatabase;
|
|
144
|
-
} else if (activeDriver === "postgres" || activeDriver === "postgresHTTP") {
|
|
145
|
-
tablesQuery = `SELECT table_name
|
|
146
|
-
FROM information_schema.tables
|
|
147
|
-
WHERE table_schema = $1
|
|
148
|
-
AND table_type = 'BASE TABLE'
|
|
149
|
-
ORDER BY table_name`;
|
|
150
|
-
tableSchemaFilter = "public";
|
|
151
|
-
} else {
|
|
152
|
-
tablesQuery = `SELECT name as table_name
|
|
153
|
-
FROM sqlite_master
|
|
154
|
-
WHERE type = 'table'
|
|
155
|
-
ORDER BY name`;
|
|
156
|
-
tableSchemaFilter = void 0;
|
|
157
|
-
}
|
|
158
|
-
const tables = await withTimeout(
|
|
159
|
-
retryQuery(() => client.query(tablesQuery, tableSchemaFilter ? [tableSchemaFilter] : [])),
|
|
160
|
-
3e4
|
|
161
|
-
);
|
|
162
|
-
console.log("Tables in the database:", tables.rows.map((t) => t.table_name).join(", "));
|
|
163
|
-
let processedTables = 0;
|
|
164
|
-
const totalTables = tables.rows.length;
|
|
165
|
-
for (const table of tables.rows) {
|
|
166
|
-
const tableName = table.table_name;
|
|
167
|
-
processedTables++;
|
|
168
|
-
console.log(`[${processedTables}/${totalTables}] Inspecting table: ${tableName}`);
|
|
169
|
-
try {
|
|
170
|
-
let columnsQuery;
|
|
171
|
-
let queryParams;
|
|
172
|
-
if (activeDriver === "mysql") {
|
|
173
|
-
columnsQuery = `
|
|
174
|
-
SELECT
|
|
175
|
-
COLUMN_NAME as column_name,
|
|
176
|
-
DATA_TYPE as data_type,
|
|
177
|
-
IS_NULLABLE as is_nullable,
|
|
178
|
-
COLUMN_DEFAULT as column_default
|
|
179
|
-
FROM information_schema.columns
|
|
180
|
-
WHERE TABLE_NAME = ?
|
|
181
|
-
AND TABLE_SCHEMA = ?
|
|
182
|
-
ORDER BY ORDINAL_POSITION
|
|
183
|
-
`;
|
|
184
|
-
queryParams = [tableName, tableSchemaFilter];
|
|
185
|
-
} else if (activeDriver === "postgres" || activeDriver === "postgresHTTP") {
|
|
186
|
-
columnsQuery = `
|
|
187
|
-
SELECT
|
|
188
|
-
column_name,
|
|
189
|
-
data_type,
|
|
190
|
-
is_nullable,
|
|
191
|
-
column_default
|
|
192
|
-
FROM information_schema.columns
|
|
193
|
-
WHERE table_name = $1
|
|
194
|
-
AND table_schema = $2
|
|
195
|
-
ORDER BY ordinal_position
|
|
196
|
-
`;
|
|
197
|
-
queryParams = [tableName, tableSchemaFilter];
|
|
198
|
-
} else {
|
|
199
|
-
columnsQuery = `
|
|
200
|
-
SELECT
|
|
201
|
-
name as column_name,
|
|
202
|
-
type as data_type,
|
|
203
|
-
CASE WHEN "notnull" = 0 THEN 'YES' ELSE 'NO' END as is_nullable,
|
|
204
|
-
dflt_value as column_default
|
|
205
|
-
FROM pragma_table_info(?)
|
|
206
|
-
ORDER BY cid
|
|
207
|
-
`;
|
|
208
|
-
queryParams = [tableName];
|
|
209
|
-
}
|
|
210
|
-
const columns = await withTimeout(
|
|
211
|
-
retryQuery(
|
|
212
|
-
() => client.query(columnsQuery, queryParams)
|
|
213
|
-
),
|
|
214
|
-
15e3
|
|
215
|
-
// Shorter timeout for individual table queries
|
|
216
|
-
);
|
|
217
|
-
if (columns.rows.length === 0) {
|
|
218
|
-
console.log(`No columns found for table: ${tableName}`);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
console.log(`Columns in ${tableName}:`, columns.rows.map((c) => `${c.column_name} (${c.data_type}${c.is_nullable === "YES" ? ", nullable" : ""})`).join(", "));
|
|
222
|
-
const uniqueColumns = /* @__PURE__ */ new Map();
|
|
223
|
-
columns.rows.forEach((col) => {
|
|
224
|
-
if (!uniqueColumns.has(col.column_name)) {
|
|
225
|
-
uniqueColumns.set(col.column_name, col);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
generatedTypes += `export interface ${tableName.charAt(0).toUpperCase() + tableName.slice(1)} {
|
|
229
|
-
`;
|
|
230
|
-
for (const col of uniqueColumns.values()) {
|
|
231
|
-
const tsType = mapDatabaseTypeToTypeScript(col.data_type, col.is_nullable === "YES", activeDriver);
|
|
232
|
-
generatedTypes += ` ${col.column_name}: ${tsType};
|
|
233
|
-
`;
|
|
234
|
-
}
|
|
235
|
-
generatedTypes += "}\n\n";
|
|
236
|
-
} catch (error) {
|
|
237
|
-
console.error(`Failed to process table ${tableName}:`, error);
|
|
238
|
-
console.log(`Skipping table ${tableName} and continuing...`);
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
generatedTypes += "export interface Database {\n";
|
|
243
|
-
for (const table of tables.rows) {
|
|
244
|
-
const tableName = table.table_name.charAt(0).toUpperCase() + table.table_name.slice(1);
|
|
245
|
-
generatedTypes += ` ${tableName}: ${tableName};
|
|
246
|
-
`;
|
|
247
|
-
}
|
|
248
|
-
generatedTypes += "}\n\n";
|
|
249
|
-
await fs.writeFile("db-types.ts", generatedTypes, "utf8");
|
|
250
|
-
console.log("TypeScript types written to db-types.ts");
|
|
251
|
-
console.log(`Successfully processed ${processedTables} tables`);
|
|
252
|
-
} catch (error) {
|
|
253
|
-
console.error("Fatal error during database inspection:", error);
|
|
254
|
-
process.exit(1);
|
|
255
|
-
}
|
|
256
|
-
process.exit(0);
|
|
257
|
-
};
|
|
28
|
+
}
|
|
29
|
+
class ConnectionError extends DatabaseError {
|
|
30
|
+
constructor(driverType, originalError) {
|
|
31
|
+
super(`Failed to connect to ${driverType}`, driverType, originalError);
|
|
32
|
+
this.name = "ConnectionError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
258
35
|
|
|
259
36
|
class PostgresDriver {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
37
|
+
client;
|
|
38
|
+
constructor(config) {
|
|
39
|
+
try {
|
|
40
|
+
if (config.experimental?.http) {
|
|
41
|
+
this.client = new PostgresHTTPDriver(config.experimental.http);
|
|
42
|
+
} else {
|
|
43
|
+
this.client = postgres(config.connectionString || "");
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new ConnectionError("postgres", error);
|
|
266
47
|
}
|
|
267
48
|
}
|
|
268
|
-
|
|
269
|
-
async query(query, params) {
|
|
49
|
+
async query(sql, params) {
|
|
270
50
|
try {
|
|
271
51
|
if (this.client instanceof PostgresHTTPDriver) {
|
|
272
|
-
return await this.client.query(
|
|
52
|
+
return await this.client.query(sql, params);
|
|
273
53
|
}
|
|
274
|
-
const result = await this.client.unsafe(
|
|
54
|
+
const result = await this.client.unsafe(sql, params || []);
|
|
275
55
|
return {
|
|
276
56
|
rows: result,
|
|
277
|
-
rowCount: result.length,
|
|
57
|
+
rowCount: Array.isArray(result) ? result.length : 0,
|
|
278
58
|
command: void 0
|
|
279
59
|
};
|
|
280
60
|
} catch (error) {
|
|
281
|
-
|
|
282
|
-
throw error;
|
|
61
|
+
throw new QueryError("postgres", sql, error);
|
|
283
62
|
}
|
|
284
63
|
}
|
|
64
|
+
async transaction(callback) {
|
|
65
|
+
if (this.client instanceof PostgresHTTPDriver) {
|
|
66
|
+
throw new Error("Transactions not supported with HTTP driver");
|
|
67
|
+
}
|
|
68
|
+
const result = await this.client.begin(async (sql) => {
|
|
69
|
+
const transactionDriver = new PostgresDriver({ connectionString: "" });
|
|
70
|
+
transactionDriver.client = sql;
|
|
71
|
+
return await callback(transactionDriver);
|
|
72
|
+
});
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
285
75
|
async close() {
|
|
286
76
|
try {
|
|
287
77
|
if (this.client instanceof PostgresHTTPDriver) {
|
|
@@ -290,30 +80,23 @@ class PostgresDriver {
|
|
|
290
80
|
await this.client.end();
|
|
291
81
|
} catch (error) {
|
|
292
82
|
console.error("Error closing Postgres client:", error);
|
|
293
|
-
throw error;
|
|
294
83
|
}
|
|
295
84
|
}
|
|
296
85
|
}
|
|
297
86
|
class PostgresHTTPDriver {
|
|
298
87
|
httpClient;
|
|
299
|
-
constructor(
|
|
300
|
-
if (!options || !options.http || !options.http.url) {
|
|
301
|
-
throw new Error("Postgres HTTP driver requires a valid URL in options.http");
|
|
302
|
-
}
|
|
88
|
+
constructor(config) {
|
|
303
89
|
this.httpClient = ky.create({
|
|
304
|
-
prefixUrl:
|
|
90
|
+
prefixUrl: config.url,
|
|
305
91
|
headers: {
|
|
306
|
-
Authorization: `Bearer ${
|
|
92
|
+
Authorization: `Bearer ${config.apiKey || ""}`
|
|
307
93
|
}
|
|
308
94
|
});
|
|
309
95
|
}
|
|
310
|
-
async query(
|
|
96
|
+
async query(sql, params) {
|
|
311
97
|
try {
|
|
312
98
|
const response = await this.httpClient.post("query", {
|
|
313
|
-
json: {
|
|
314
|
-
query,
|
|
315
|
-
params
|
|
316
|
-
}
|
|
99
|
+
json: { query: sql, params }
|
|
317
100
|
}).json();
|
|
318
101
|
return {
|
|
319
102
|
rows: response.rows,
|
|
@@ -321,8 +104,7 @@ class PostgresHTTPDriver {
|
|
|
321
104
|
command: void 0
|
|
322
105
|
};
|
|
323
106
|
} catch (error) {
|
|
324
|
-
|
|
325
|
-
throw error;
|
|
107
|
+
throw new QueryError("postgres-http", sql, error);
|
|
326
108
|
}
|
|
327
109
|
}
|
|
328
110
|
async close() {
|
|
@@ -331,17 +113,40 @@ class PostgresHTTPDriver {
|
|
|
331
113
|
}
|
|
332
114
|
|
|
333
115
|
class LibSQLDriver {
|
|
334
|
-
constructor(options) {
|
|
335
|
-
this.options = options;
|
|
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
116
|
client;
|
|
117
|
+
constructor(config) {
|
|
118
|
+
try {
|
|
119
|
+
this.client = config.useTursoServerlessDriver ? createClient({
|
|
120
|
+
url: config.url,
|
|
121
|
+
...config.authToken ? { authToken: config.authToken } : {}
|
|
122
|
+
}) : createClient$1({
|
|
123
|
+
url: config.url,
|
|
124
|
+
...config.authToken ? { authToken: config.authToken } : {}
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new ConnectionError("libsql", error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async query(sql, params) {
|
|
131
|
+
try {
|
|
132
|
+
const result = await this.client.execute(sql, params);
|
|
133
|
+
return this.convertLibsqlResult(result);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new QueryError("libsql", sql, error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async transaction(callback) {
|
|
139
|
+
const transactionDriver = new LibSQLDriver({ url: "", authToken: "" });
|
|
140
|
+
transactionDriver.client = this.client;
|
|
141
|
+
return await callback(transactionDriver);
|
|
142
|
+
}
|
|
143
|
+
async close() {
|
|
144
|
+
try {
|
|
145
|
+
this.client.close();
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("Error closing LibSQL client:", error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
345
150
|
convertLibsqlResult(result) {
|
|
346
151
|
const rows = result.rows.map((row) => {
|
|
347
152
|
const obj = {};
|
|
@@ -357,158 +162,241 @@ class LibSQLDriver {
|
|
|
357
162
|
fields: result.columns.map((col) => ({ name: col, dataTypeID: 0 }))
|
|
358
163
|
};
|
|
359
164
|
}
|
|
360
|
-
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class MySQLDriver {
|
|
168
|
+
client;
|
|
169
|
+
constructor(config) {
|
|
170
|
+
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.");
|
|
171
|
+
try {
|
|
172
|
+
this.client = mysql.createConnection(config.connectionString);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
throw new ConnectionError("mysql", error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async query(sql, params) {
|
|
361
178
|
try {
|
|
362
|
-
const
|
|
363
|
-
|
|
179
|
+
const [rows, fields] = await (await this.client).execute(sql, params || []);
|
|
180
|
+
const rowCount = Array.isArray(rows) ? rows.length : 0;
|
|
181
|
+
const normalizedFields = fields.map((field) => ({
|
|
182
|
+
name: field.name,
|
|
183
|
+
dataTypeID: field.columnType
|
|
184
|
+
}));
|
|
185
|
+
return {
|
|
186
|
+
rows,
|
|
187
|
+
rowCount,
|
|
188
|
+
command: void 0,
|
|
189
|
+
fields: normalizedFields
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
throw new QueryError("mysql", sql, error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async transaction(callback) {
|
|
196
|
+
const connection = await this.client;
|
|
197
|
+
try {
|
|
198
|
+
await connection.beginTransaction();
|
|
199
|
+
const transactionDriver = new MySQLDriver({ connectionString: "" });
|
|
200
|
+
transactionDriver.client = Promise.resolve(connection);
|
|
201
|
+
const result = await callback(transactionDriver);
|
|
202
|
+
await connection.commit();
|
|
203
|
+
return result;
|
|
364
204
|
} catch (error) {
|
|
365
|
-
|
|
205
|
+
await connection.rollback();
|
|
366
206
|
throw error;
|
|
367
207
|
}
|
|
368
208
|
}
|
|
369
209
|
async close() {
|
|
370
210
|
try {
|
|
371
|
-
this.client.
|
|
211
|
+
await (await this.client).end();
|
|
372
212
|
} catch (error) {
|
|
373
|
-
|
|
213
|
+
consola.error("Error closing MySQL client:", error);
|
|
374
214
|
}
|
|
375
215
|
}
|
|
376
216
|
}
|
|
377
217
|
|
|
378
|
-
class
|
|
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
|
-
}
|
|
218
|
+
class SqliteDriver {
|
|
384
219
|
client;
|
|
385
|
-
|
|
220
|
+
constructor(config) {
|
|
386
221
|
try {
|
|
387
|
-
|
|
388
|
-
|
|
222
|
+
this.client = new Database(config.filename, {
|
|
223
|
+
readonly: config.readonly || false,
|
|
224
|
+
fileMustExist: config.readonly || false
|
|
225
|
+
});
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throw new ConnectionError("sqlite", error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async query(sql, params) {
|
|
231
|
+
try {
|
|
232
|
+
const stmt = this.client.prepare(sql);
|
|
233
|
+
const rows = stmt.all(params || []);
|
|
234
|
+
const fields = rows.length > 0 && typeof rows[0] === "object" && rows[0] !== null ? Object.keys(rows[0]).map((name) => ({ name, dataTypeID: 0 })) : [];
|
|
389
235
|
return {
|
|
390
236
|
rows,
|
|
391
|
-
rowCount,
|
|
237
|
+
rowCount: rows.length,
|
|
392
238
|
command: void 0,
|
|
393
|
-
fields
|
|
239
|
+
fields
|
|
394
240
|
};
|
|
395
241
|
} catch (error) {
|
|
396
|
-
|
|
397
|
-
throw error;
|
|
242
|
+
throw new QueryError("sqlite", sql, error);
|
|
398
243
|
}
|
|
399
244
|
}
|
|
245
|
+
async transaction(callback) {
|
|
246
|
+
const transaction = this.client.transaction(() => {
|
|
247
|
+
const transactionDriver = new SqliteDriver({ filename: "" });
|
|
248
|
+
transactionDriver.client = this.client;
|
|
249
|
+
return callback(transactionDriver);
|
|
250
|
+
});
|
|
251
|
+
return await transaction();
|
|
252
|
+
}
|
|
253
|
+
async prepare(sql) {
|
|
254
|
+
return new SqlitePreparedStatement(this.client.prepare(sql));
|
|
255
|
+
}
|
|
400
256
|
async close() {
|
|
401
257
|
try {
|
|
402
|
-
|
|
258
|
+
this.client.close();
|
|
403
259
|
} catch (error) {
|
|
404
|
-
|
|
260
|
+
console.error("Error closing SQLite client:", error);
|
|
405
261
|
}
|
|
406
262
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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) {
|
|
263
|
+
// SQLite-specific methods
|
|
264
|
+
exec(sql) {
|
|
265
|
+
this.client.exec(sql);
|
|
266
|
+
}
|
|
267
|
+
backup(filename) {
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
425
269
|
try {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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;
|
|
270
|
+
this.client.backup(filename);
|
|
271
|
+
resolve();
|
|
272
|
+
} catch (error) {
|
|
273
|
+
reject(error);
|
|
436
274
|
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
pragma(pragma) {
|
|
278
|
+
return this.client.pragma(pragma);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
class SqlitePreparedStatement {
|
|
282
|
+
constructor(stmt) {
|
|
283
|
+
this.stmt = stmt;
|
|
284
|
+
}
|
|
285
|
+
async execute(params) {
|
|
286
|
+
try {
|
|
287
|
+
const rows = this.stmt.all(params || []);
|
|
288
|
+
const fields = rows.length > 0 && typeof rows[0] === "object" && rows[0] !== null ? Object.keys(rows[0]).map((name) => ({ name, dataTypeID: 0 })) : [];
|
|
289
|
+
return {
|
|
290
|
+
rows,
|
|
291
|
+
rowCount: rows.length,
|
|
292
|
+
command: void 0,
|
|
293
|
+
fields
|
|
294
|
+
};
|
|
295
|
+
} catch (error) {
|
|
296
|
+
throw new QueryError("sqlite", "prepared statement", error);
|
|
437
297
|
}
|
|
438
|
-
|
|
298
|
+
}
|
|
299
|
+
async finalize() {
|
|
300
|
+
return Promise.resolve();
|
|
301
|
+
}
|
|
302
|
+
// SQLite-specific methods for prepared statements
|
|
303
|
+
run(params) {
|
|
304
|
+
return this.stmt.run(params || []);
|
|
305
|
+
}
|
|
306
|
+
get(params) {
|
|
307
|
+
return this.stmt.get(params || []);
|
|
308
|
+
}
|
|
309
|
+
all(params) {
|
|
310
|
+
return this.stmt.all(params || []);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
class SQLClient {
|
|
315
|
+
primaryDriver;
|
|
316
|
+
fallbackDrivers;
|
|
317
|
+
constructor(options) {
|
|
318
|
+
this.primaryDriver = options.driver;
|
|
319
|
+
this.fallbackDrivers = options.fallbackDrivers || [];
|
|
320
|
+
}
|
|
321
|
+
async query(sql, params) {
|
|
322
|
+
const drivers = [this.primaryDriver, ...this.fallbackDrivers];
|
|
323
|
+
let lastError;
|
|
324
|
+
for (const driver of drivers) {
|
|
439
325
|
try {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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;
|
|
326
|
+
return await driver.query(sql, params);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
lastError = error;
|
|
329
|
+
consola.warn(`Query failed with ${driver.constructor.name}:`, error);
|
|
330
|
+
continue;
|
|
450
331
|
}
|
|
451
332
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
333
|
+
throw lastError || new DatabaseError("All drivers failed to execute query", "unknown");
|
|
334
|
+
}
|
|
335
|
+
async transaction(callback) {
|
|
336
|
+
if (!hasTransactionSupport(this.primaryDriver)) {
|
|
337
|
+
throw new DatabaseError("Primary driver does not support transactions", this.primaryDriver.constructor.name);
|
|
338
|
+
}
|
|
339
|
+
return await this.primaryDriver.transaction(async (transactionDriver) => {
|
|
340
|
+
const transactionClient = new SQLClient({
|
|
341
|
+
driver: transactionDriver,
|
|
342
|
+
fallbackDrivers: []
|
|
343
|
+
});
|
|
344
|
+
return await callback(transactionClient);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
async prepare(sql) {
|
|
348
|
+
if (!hasPreparedStatementSupport(this.primaryDriver)) {
|
|
349
|
+
throw new DatabaseError("Primary driver does not support prepared statements", this.primaryDriver.constructor.name);
|
|
465
350
|
}
|
|
466
|
-
|
|
467
|
-
consola.error(error);
|
|
468
|
-
throw error;
|
|
351
|
+
return await this.primaryDriver.prepare(sql);
|
|
469
352
|
}
|
|
353
|
+
// Helper methods for common database operations
|
|
470
354
|
async findFirst(table, where) {
|
|
471
355
|
const tableName = String(table);
|
|
472
356
|
const whereEntries = Object.entries(where || {});
|
|
473
|
-
let
|
|
474
|
-
let
|
|
357
|
+
let sql = `SELECT * FROM ${tableName}`;
|
|
358
|
+
let params = [];
|
|
475
359
|
if (whereEntries.length > 0) {
|
|
476
|
-
const whereClause = whereEntries.map((
|
|
477
|
-
|
|
478
|
-
|
|
360
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(" AND ");
|
|
361
|
+
sql += ` WHERE ${whereClause}`;
|
|
362
|
+
params = whereEntries.map(([, value]) => value);
|
|
479
363
|
}
|
|
480
|
-
|
|
481
|
-
const result = await this.query(
|
|
364
|
+
sql += " LIMIT 1";
|
|
365
|
+
const result = await this.query(sql, params);
|
|
482
366
|
return result.rows[0] || null;
|
|
483
367
|
}
|
|
484
368
|
async findMany(table, options) {
|
|
485
369
|
const tableName = String(table);
|
|
486
|
-
const { where, limit } = options || {};
|
|
370
|
+
const { where, limit, offset } = options || {};
|
|
487
371
|
const whereEntries = Object.entries(where || {});
|
|
488
|
-
let
|
|
489
|
-
let
|
|
372
|
+
let sql = `SELECT * FROM ${tableName}`;
|
|
373
|
+
let params = [];
|
|
490
374
|
if (whereEntries.length > 0) {
|
|
491
|
-
const whereClause = whereEntries.map((
|
|
492
|
-
|
|
493
|
-
|
|
375
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(" AND ");
|
|
376
|
+
sql += ` WHERE ${whereClause}`;
|
|
377
|
+
params = whereEntries.map(([, value]) => value);
|
|
494
378
|
}
|
|
495
379
|
if (typeof limit === "number" && limit > 0) {
|
|
496
|
-
|
|
497
|
-
|
|
380
|
+
sql += ` LIMIT $${params.length + 1}`;
|
|
381
|
+
params.push(limit);
|
|
382
|
+
}
|
|
383
|
+
if (typeof offset === "number" && offset > 0) {
|
|
384
|
+
sql += ` OFFSET $${params.length + 1}`;
|
|
385
|
+
params.push(offset);
|
|
498
386
|
}
|
|
499
|
-
const result = await this.query(
|
|
387
|
+
const result = await this.query(sql, params);
|
|
500
388
|
return result.rows;
|
|
501
389
|
}
|
|
502
390
|
async insert(table, data) {
|
|
503
391
|
const tableName = String(table);
|
|
504
392
|
const keys = Object.keys(data);
|
|
505
|
-
const values = Object.values(data)
|
|
393
|
+
const values = Object.values(data);
|
|
506
394
|
if (keys.length === 0) {
|
|
507
395
|
throw new Error("No data provided for insert");
|
|
508
396
|
}
|
|
509
397
|
const placeholders = keys.map((_, index) => `$${index + 1}`).join(", ");
|
|
510
|
-
const
|
|
511
|
-
const result = await this.query(
|
|
398
|
+
const sql = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders}) RETURNING *`;
|
|
399
|
+
const result = await this.query(sql, values);
|
|
512
400
|
if (!result.rows[0]) {
|
|
513
401
|
throw new Error("Insert failed: No data returned");
|
|
514
402
|
}
|
|
@@ -524,11 +412,11 @@ class DriftSQLClient {
|
|
|
524
412
|
if (whereEntries.length === 0) {
|
|
525
413
|
throw new Error("No conditions provided for update");
|
|
526
414
|
}
|
|
527
|
-
const setClause = setEntries.map((
|
|
528
|
-
const whereClause = whereEntries.map((
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
const result = await this.query(
|
|
415
|
+
const setClause = setEntries.map((_, index) => `${setEntries[index]?.[0]} = $${index + 1}`).join(", ");
|
|
416
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${setEntries.length + index + 1}`).join(" AND ");
|
|
417
|
+
const sql = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause} RETURNING *`;
|
|
418
|
+
const params = [...setEntries.map(([, value]) => value), ...whereEntries.map(([, value]) => value)];
|
|
419
|
+
const result = await this.query(sql, params);
|
|
532
420
|
return result.rows[0] || null;
|
|
533
421
|
}
|
|
534
422
|
async delete(table, where) {
|
|
@@ -537,26 +425,44 @@ class DriftSQLClient {
|
|
|
537
425
|
if (whereEntries.length === 0) {
|
|
538
426
|
throw new Error("No conditions provided for delete");
|
|
539
427
|
}
|
|
540
|
-
const whereClause = whereEntries.map((
|
|
541
|
-
const
|
|
542
|
-
const
|
|
543
|
-
const result = await this.query(
|
|
544
|
-
return
|
|
428
|
+
const whereClause = whereEntries.map((_, index) => `${whereEntries[index]?.[0]} = $${index + 1}`).join(" AND ");
|
|
429
|
+
const sql = `DELETE FROM ${tableName} WHERE ${whereClause}`;
|
|
430
|
+
const params = whereEntries.map(([, value]) => value);
|
|
431
|
+
const result = await this.query(sql, params);
|
|
432
|
+
return result.rowCount || 0;
|
|
545
433
|
}
|
|
546
|
-
|
|
547
|
-
|
|
434
|
+
// Get the primary driver (useful for driver-specific operations)
|
|
435
|
+
getDriver() {
|
|
436
|
+
return this.primaryDriver;
|
|
437
|
+
}
|
|
438
|
+
// Check driver capabilities
|
|
439
|
+
supportsTransactions() {
|
|
440
|
+
return hasTransactionSupport(this.primaryDriver);
|
|
441
|
+
}
|
|
442
|
+
supportsPreparedStatements() {
|
|
443
|
+
return hasPreparedStatementSupport(this.primaryDriver);
|
|
548
444
|
}
|
|
549
445
|
async close() {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
if (this.libsql) {
|
|
554
|
-
this.libsql.close();
|
|
555
|
-
}
|
|
556
|
-
if (this.mysql) {
|
|
557
|
-
await this.mysql.close();
|
|
558
|
-
}
|
|
446
|
+
const drivers = [this.primaryDriver, ...this.fallbackDrivers];
|
|
447
|
+
await Promise.all(drivers.map((driver) => driver.close().catch((err) => consola.warn(`Error closing ${driver.constructor.name}:`, err))));
|
|
559
448
|
}
|
|
560
449
|
}
|
|
450
|
+
function createPostgresClient(config) {
|
|
451
|
+
const { PostgresDriver: PostgresDriver2 } = require("./drivers/postgres");
|
|
452
|
+
return new SQLClient({ driver: new PostgresDriver2(config) });
|
|
453
|
+
}
|
|
454
|
+
function createLibSQLClient(config) {
|
|
455
|
+
const { LibSQLDriver: LibSQLDriver2 } = require("./drivers/libsql");
|
|
456
|
+
return new SQLClient({ driver: new LibSQLDriver2(config) });
|
|
457
|
+
}
|
|
458
|
+
function createMySQLClient(config) {
|
|
459
|
+
const { MySQLDriver: MySQLDriver2 } = require("./drivers/mysql");
|
|
460
|
+
return new SQLClient({ driver: new MySQLDriver2(config) });
|
|
461
|
+
}
|
|
462
|
+
function createSqliteClient(config) {
|
|
463
|
+
const { SqliteDriver: SqliteDriver2 } = require("./drivers/sqlite");
|
|
464
|
+
return new SQLClient({ driver: new SqliteDriver2(config) });
|
|
465
|
+
}
|
|
466
|
+
const DriftSQLClient = SQLClient;
|
|
561
467
|
|
|
562
|
-
export { DriftSQLClient,
|
|
468
|
+
export { DriftSQLClient, LibSQLDriver, MySQLDriver, PostgresDriver, SQLClient, SqliteDriver, createLibSQLClient, createMySQLClient, createPostgresClient, createSqliteClient };
|