driftsql 1.0.12 → 1.0.14

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