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