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/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 fs from 'node:fs/promises';
7
+ import Database from 'better-sqlite3';
7
8
 
8
- const supportedDrivers = ["postgres", "postgresHTTP", "mysql", "libsql"];
9
- const withTimeout = (promise, timeoutMs = 3e4) => {
10
- return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error(`Query timeout after ${timeoutMs}ms`)), timeoutMs))]);
11
- };
12
- const retryQuery = async (queryFn, maxRetries = 3, baseDelay = 1e3) => {
13
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
14
- try {
15
- return await queryFn();
16
- } catch (error) {
17
- if (attempt === maxRetries) {
18
- throw error;
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
- throw new Error("Max retries exceeded");
26
- };
27
- const mapDatabaseTypeToTypeScript = (dataType, isNullable = false, driverType = "postgres") => {
28
- const nullable = isNullable ? " | null" : "";
29
- const lowerType = dataType.toLowerCase();
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
- const inspectDB = async (drivers) => {
111
- if (!drivers) throw new Error("No drivers provided for inspection");
112
- const configuredDrivers = Object.keys(drivers).filter((key) => drivers[key] !== void 0);
113
- if (configuredDrivers.length === 0) {
114
- throw new Error("No drivers are configured");
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
- async query(query, params) {
38
+ constructor(config) {
265
39
  try {
266
- const result = await this.client.unsafe(query, params);
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
- console.error("Postgres query error:", error);
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
- class LibSQLDriver {
288
- constructor(options) {
289
- this.options = options;
290
- this.client = this.options?.options?.experimental?.useTursoServerlessDriver ? createClient({
291
- url: this.options.url,
292
- ...this.options.authToken ? { authToken: this.options.authToken } : {}
293
- }) : createClient$1({
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
- async query(query, params) {
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 result = await this.client.execute(query, params);
317
- return this.convertLibsqlResult(result);
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
- console.error("LibSQL query error:", error);
205
+ await connection.rollback();
320
206
  throw error;
321
207
  }
322
208
  }
323
209
  async close() {
324
210
  try {
325
- this.client.close();
211
+ await (await this.client).end();
326
212
  } catch (error) {
327
- console.error("Error closing LibSQL client:", error);
213
+ consola.error("Error closing MySQL client:", error);
328
214
  }
329
215
  }
330
216
  }
331
217
 
332
- class MySQLDriver {
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
- async query(query, params) {
220
+ constructor(config) {
340
221
  try {
341
- const [rows, fields] = await (await this.client).execute(query, params || []);
342
- const rowCount = Array.isArray(rows) ? rows.length : 0;
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: fields.map((field) => ({ name: field.name, dataTypeID: field.columnType }))
239
+ fields
348
240
  };
349
241
  } catch (error) {
350
- consola.error("MySQL query error:", error);
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
- await (await this.client).end();
258
+ this.client.close();
357
259
  } catch (error) {
358
- consola.error("Error closing MySQL client:", error);
260
+ console.error("Error closing SQLite client:", error);
359
261
  }
360
262
  }
361
- }
362
-
363
- class DriftSQLClient {
364
- postgres;
365
- libsql;
366
- mysql;
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
- const result = await this.postgres.query(query, args || []);
381
- return {
382
- rows: result.rows,
383
- rowCount: result.rowCount || 0,
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
- if (this.mysql) {
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
- const result = await this.mysql.query(query, args || []);
395
- return {
396
- rows: result.rows,
397
- rowCount: result.rowCount || 0,
398
- command: result.command,
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
- if (this.libsql) {
407
- try {
408
- const result = await this.libsql.query(query, args || []);
409
- return {
410
- rows: result.rows,
411
- rowCount: result.rowCount || 0,
412
- command: result.command,
413
- fields: void 0
414
- };
415
- } catch (error2) {
416
- consola.error("Failed to execute query with LibSQL:", error2);
417
- throw error2;
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
- const error = new Error("No database driver is configured or all drivers failed to execute the query");
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 query = `SELECT * FROM ${tableName}`;
428
- let args = [];
357
+ let sql = `SELECT * FROM ${tableName}`;
358
+ let params = [];
429
359
  if (whereEntries.length > 0) {
430
- const whereClause = whereEntries.map(([key], index) => `${key} = $${index + 1}`).join(" AND ");
431
- query += ` WHERE ${whereClause}`;
432
- args = whereEntries.map(([, value]) => value);
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
- query += " LIMIT 1";
435
- const result = await this.query(query, args);
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 query = `SELECT * FROM ${tableName}`;
443
- let args = [];
372
+ let sql = `SELECT * FROM ${tableName}`;
373
+ let params = [];
444
374
  if (whereEntries.length > 0) {
445
- const whereClause = whereEntries.map(([key], index) => `${key} = $${index + 1}`).join(" AND ");
446
- query += ` WHERE ${whereClause}`;
447
- args = whereEntries.map(([, value]) => value);
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
- query += ` LIMIT $${args.length + 1}`;
451
- args.push(limit);
380
+ sql += ` LIMIT $${params.length + 1}`;
381
+ params.push(limit);
452
382
  }
453
- const result = await this.query(query, args);
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).map((value) => value);
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 query = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders}) RETURNING *`;
465
- const result = await this.query(query, values);
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(([key], index) => `${key} = $${index + 1}`).join(", ");
482
- const whereClause = whereEntries.map(([key], index) => `${key} = $${setEntries.length + index + 1}`).join(" AND ");
483
- const query = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause} RETURNING *`;
484
- const args = [...setEntries.map(([, value]) => value), ...whereEntries.map(([, value]) => value)];
485
- const result = await this.query(query, args);
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(([key], index) => `${key} = $${index + 1}`).join(" AND ");
495
- const query = `DELETE FROM ${tableName} WHERE ${whereClause}`;
496
- const args = whereEntries.map(([, value]) => value);
497
- const result = await this.query(query, args);
498
- return (result.rowCount || 0) > 0;
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
- deleteFirst(table, where) {
501
- return this.delete(table, where);
442
+ supportsPreparedStatements() {
443
+ return hasPreparedStatementSupport(this.primaryDriver);
502
444
  }
503
445
  async close() {
504
- if (this.postgres) {
505
- await this.postgres.close();
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, inspectDB };
468
+ export { DriftSQLClient, LibSQLDriver, MySQLDriver, PostgresDriver, SQLClient, SqliteDriver, createLibSQLClient, createMySQLClient, createPostgresClient, createSqliteClient };