driftsql 1.0.18 → 1.0.19

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