@pineliner/odb-client 1.0.1 → 1.0.3

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.cjs CHANGED
@@ -1,7 +1,22 @@
1
1
  "use strict";
2
2
  // The require scope
3
3
  var __webpack_require__ = {};
4
- /************************************************************************/ // webpack/runtime/define_property_getters
4
+ /************************************************************************/ // webpack/runtime/compat_get_default_export
5
+ (()=>{
6
+ // getDefaultExport function for compatibility with non-ESM modules
7
+ __webpack_require__.n = function(module) {
8
+ var getter = module && module.__esModule ? function() {
9
+ return module['default'];
10
+ } : function() {
11
+ return module;
12
+ };
13
+ __webpack_require__.d(getter, {
14
+ a: getter
15
+ });
16
+ return getter;
17
+ };
18
+ })();
19
+ // webpack/runtime/define_property_getters
5
20
  (()=>{
6
21
  __webpack_require__.d = function(exports1, definition) {
7
22
  for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
@@ -33,25 +48,31 @@ var __webpack_require__ = {};
33
48
  __webpack_require__.r(__webpack_exports__);
34
49
  // EXPORTS
35
50
  __webpack_require__.d(__webpack_exports__, {
36
- identifier: ()=>/* binding */ src_identifier,
51
+ fragment: ()=>/* reexport */ fragment,
37
52
  HTTPClient: ()=>/* reexport */ HTTPClient,
38
53
  default: ()=>/* reexport */ odblite,
39
- ServiceClient: ()=>/* reexport */ ServiceClient,
54
+ identifier: ()=>/* binding */ src_identifier,
55
+ ODBLiteAdapter: ()=>/* reexport */ ODBLiteAdapter,
56
+ BunSQLiteAdapter: ()=>/* reexport */ BunSQLiteAdapter,
40
57
  raw: ()=>/* binding */ src_raw,
41
- sql: ()=>/* reexport */ sql_parser_sql,
42
58
  insertValues: ()=>/* binding */ src_insertValues,
59
+ sql: ()=>/* reexport */ sql_parser_sql,
43
60
  where: ()=>/* binding */ src_where,
61
+ DatabaseManager: ()=>/* reexport */ DatabaseManager,
62
+ LibSQLAdapter: ()=>/* reexport */ LibSQLAdapter,
44
63
  updateSet: ()=>/* binding */ src_updateSet,
45
64
  ODBLiteClient: ()=>/* reexport */ ODBLiteClient,
46
65
  ODBLiteTransaction: ()=>/* reexport */ ODBLiteTransaction,
47
66
  join: ()=>/* binding */ src_join,
48
67
  ODBLiteError: ()=>/* reexport */ ODBLiteError,
68
+ parseSQL: ()=>/* reexport */ parseSQL,
49
69
  ConnectionError: ()=>/* reexport */ ConnectionError,
50
70
  SQLParser: ()=>/* reexport */ sql_parser_SQLParser,
51
71
  SimpleTransaction: ()=>/* reexport */ SimpleTransaction,
52
72
  odblite: ()=>/* reexport */ odblite,
73
+ splitSQLStatements: ()=>/* reexport */ splitSQLStatements,
53
74
  QueryError: ()=>/* reexport */ types_QueryError,
54
- fragment: ()=>/* reexport */ fragment
75
+ ServiceClient: ()=>/* reexport */ ServiceClient
55
76
  });
56
77
  // Core types for the ODBLite client
57
78
  // Error types
@@ -913,19 +934,20 @@ ODBLiteClient.join;
913
934
  * Create a new database
914
935
  *
915
936
  * @param name - Database name (should be unique)
916
- * @param nodeId - ID of the node to host the database
937
+ * @param nodeId - ID of the node to host the database (optional - server will select if null)
917
938
  * @returns Created database object with hash
918
939
  */ async createDatabase(name, nodeId) {
940
+ const body = {
941
+ name
942
+ };
943
+ if (nodeId) body.nodeId = nodeId;
919
944
  const response = await fetch(`${this.apiUrl}/api/tenant/databases`, {
920
945
  method: 'POST',
921
946
  headers: {
922
947
  Authorization: `Bearer ${this.apiKey}`,
923
948
  'Content-Type': 'application/json'
924
949
  },
925
- body: JSON.stringify({
926
- name,
927
- nodeId
928
- })
950
+ body: JSON.stringify(body)
929
951
  });
930
952
  const result = await response.json();
931
953
  if (!result.success) throw new Error(result.error || 'Failed to create database');
@@ -1053,9 +1075,738 @@ ODBLiteClient.join;
1053
1075
  this.databaseCache.set(cacheKey, hash);
1054
1076
  }
1055
1077
  }
1078
+ const external_bun_sqlite_namespaceObject = require("bun:sqlite");
1079
+ /**
1080
+ * Bun SQLite adapter for DatabaseManager
1081
+ * Wraps bun:sqlite with Connection interface
1082
+ */ class BunSQLiteAdapter {
1083
+ type = 'bun-sqlite';
1084
+ config;
1085
+ constructor(config){
1086
+ this.config = config;
1087
+ }
1088
+ async connect(config) {
1089
+ const db = new external_bun_sqlite_namespaceObject.Database(config.databasePath, {
1090
+ readonly: config.readonly,
1091
+ create: config.create ?? true
1092
+ });
1093
+ return new BunSQLiteConnection(db);
1094
+ }
1095
+ async disconnect(tenantId) {
1096
+ // bun:sqlite doesn't require explicit disconnection
1097
+ // File handles are cleaned up by GC
1098
+ }
1099
+ async isHealthy() {
1100
+ try {
1101
+ const db = new external_bun_sqlite_namespaceObject.Database(this.config.databasePath, {
1102
+ readonly: true,
1103
+ create: false
1104
+ });
1105
+ db.query('SELECT 1').get();
1106
+ db.close();
1107
+ return true;
1108
+ } catch {
1109
+ return false;
1110
+ }
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Connection implementation for Bun SQLite
1115
+ */ class BunSQLiteConnection {
1116
+ db;
1117
+ inTransaction = false;
1118
+ constructor(db){
1119
+ this.db = db;
1120
+ }
1121
+ /**
1122
+ * Template tag query (postgres.js-like)
1123
+ */ async query(strings, ...values) {
1124
+ // Build SQL from template
1125
+ const sql = strings.reduce((acc, str, i)=>acc + str + (i < values.length ? '?' : ''), '');
1126
+ return this.execute(sql, values);
1127
+ }
1128
+ /**
1129
+ * Execute SQL with parameters
1130
+ */ async execute(sql, params = []) {
1131
+ try {
1132
+ const stmt = this.db.query(sql);
1133
+ const isSelect = sql.trim().toUpperCase().startsWith('SELECT');
1134
+ if (isSelect) {
1135
+ const rows = stmt.all(...params);
1136
+ return {
1137
+ rows: rows,
1138
+ rowsAffected: 0
1139
+ };
1140
+ }
1141
+ {
1142
+ const result = stmt.run(...params);
1143
+ return {
1144
+ rows: [],
1145
+ rowsAffected: result.changes || 0,
1146
+ lastInsertRowid: result.lastInsertRowid
1147
+ };
1148
+ }
1149
+ } catch (error) {
1150
+ throw new Error(`SQL execution failed: ${error.message}`);
1151
+ }
1152
+ }
1153
+ /**
1154
+ * Prepare statement for repeated execution
1155
+ */ prepare(sql) {
1156
+ const stmt = this.db.query(sql);
1157
+ return {
1158
+ execute: async (params = [])=>{
1159
+ const isSelect = sql.trim().toUpperCase().startsWith('SELECT');
1160
+ if (isSelect) {
1161
+ const rows = stmt.all(...params);
1162
+ return {
1163
+ rows: rows,
1164
+ rowsAffected: 0
1165
+ };
1166
+ }
1167
+ {
1168
+ const result = stmt.run(...params);
1169
+ return {
1170
+ rows: [],
1171
+ rowsAffected: result.changes || 0,
1172
+ lastInsertRowid: result.lastInsertRowid
1173
+ };
1174
+ }
1175
+ },
1176
+ all: async (params = [])=>stmt.all(...params),
1177
+ get: async (params = [])=>stmt.get(...params)
1178
+ };
1179
+ }
1180
+ /**
1181
+ * Execute function in transaction
1182
+ */ async transaction(fn) {
1183
+ if (this.inTransaction) // Nested transaction - just execute the function
1184
+ return fn(this);
1185
+ this.inTransaction = true;
1186
+ try {
1187
+ await this.execute('BEGIN');
1188
+ const result = await fn(this);
1189
+ await this.execute('COMMIT');
1190
+ return result;
1191
+ } catch (error) {
1192
+ await this.execute('ROLLBACK');
1193
+ throw error;
1194
+ } finally{
1195
+ this.inTransaction = false;
1196
+ }
1197
+ }
1198
+ /**
1199
+ * Close connection
1200
+ */ async close() {
1201
+ this.db.close();
1202
+ }
1203
+ }
1204
+ const external_libsql_client_namespaceObject = require("@libsql/client");
1205
+ /**
1206
+ * LibSQL adapter for DatabaseManager
1207
+ * Wraps @libsql/client with Connection interface
1208
+ */ class LibSQLAdapter {
1209
+ type = 'libsql';
1210
+ config;
1211
+ constructor(config){
1212
+ this.config = config;
1213
+ }
1214
+ async connect(config) {
1215
+ const client = (0, external_libsql_client_namespaceObject.createClient)({
1216
+ url: config.url,
1217
+ authToken: config.authToken,
1218
+ encryptionKey: config.encryptionKey
1219
+ });
1220
+ return new LibSQLConnection(client);
1221
+ }
1222
+ async disconnect(tenantId) {
1223
+ // LibSQL clients don't require explicit disconnection
1224
+ // Connections are pooled internally
1225
+ }
1226
+ async isHealthy() {
1227
+ try {
1228
+ const client = (0, external_libsql_client_namespaceObject.createClient)({
1229
+ url: this.config.url,
1230
+ authToken: this.config.authToken
1231
+ });
1232
+ await client.execute('SELECT 1');
1233
+ return true;
1234
+ } catch {
1235
+ return false;
1236
+ }
1237
+ }
1238
+ }
1239
+ /**
1240
+ * Connection implementation for LibSQL
1241
+ */ class LibSQLConnection {
1242
+ client;
1243
+ txClient;
1244
+ constructor(client){
1245
+ this.client = client;
1246
+ }
1247
+ /**
1248
+ * Template tag query (postgres.js-like)
1249
+ */ async query(strings, ...values) {
1250
+ // Build SQL from template
1251
+ const sql = strings.reduce((acc, str, i)=>acc + str + (i < values.length ? '?' : ''), '');
1252
+ return this.execute(sql, values);
1253
+ }
1254
+ /**
1255
+ * Execute SQL with parameters
1256
+ * Supports both formats: execute(sql, params) and execute({sql, args})
1257
+ */ async execute(sql, params = []) {
1258
+ try {
1259
+ const target = this.txClient || this.client;
1260
+ // Support both execute(sql, params) and execute({sql, args}) formats
1261
+ let query;
1262
+ query = 'string' == typeof sql ? {
1263
+ sql,
1264
+ args: params
1265
+ } : {
1266
+ sql: sql.sql,
1267
+ args: sql.args || []
1268
+ };
1269
+ const result = await target.execute(query);
1270
+ return {
1271
+ rows: result.rows,
1272
+ rowsAffected: Number(result.rowsAffected),
1273
+ lastInsertRowid: result.lastInsertRowid ? BigInt(result.lastInsertRowid.toString()) : void 0
1274
+ };
1275
+ } catch (error) {
1276
+ throw new Error(`SQL execution failed: ${error.message}`);
1277
+ }
1278
+ }
1279
+ /**
1280
+ * Prepare statement for repeated execution
1281
+ */ prepare(sql) {
1282
+ return {
1283
+ execute: async (params = [])=>this.execute(sql, params),
1284
+ all: async (params = [])=>{
1285
+ const result = await this.execute(sql, params);
1286
+ return result.rows;
1287
+ },
1288
+ get: async (params = [])=>{
1289
+ const result = await this.execute(sql, params);
1290
+ return result.rows[0] || null;
1291
+ }
1292
+ };
1293
+ }
1294
+ /**
1295
+ * Execute function in transaction
1296
+ */ async transaction(fn) {
1297
+ if (this.txClient) // Nested transaction - just execute the function
1298
+ return fn(this);
1299
+ // Use manual BEGIN/COMMIT/ROLLBACK for simplicity
1300
+ try {
1301
+ await this.execute('BEGIN');
1302
+ const result = await fn(this);
1303
+ await this.execute('COMMIT');
1304
+ return result;
1305
+ } catch (error) {
1306
+ await this.execute('ROLLBACK');
1307
+ throw error;
1308
+ }
1309
+ }
1310
+ /**
1311
+ * Close connection
1312
+ */ async close() {
1313
+ // LibSQL client.close() if available in future versions
1314
+ // For now, connections are managed by the client pool
1315
+ }
1316
+ }
1317
+ /**
1318
+ * ODB-Lite adapter for DatabaseManager
1319
+ * Wraps ServiceClient and ODBLiteClient with Connection interface
1320
+ */ class ODBLiteAdapter {
1321
+ type = 'odblite';
1322
+ config;
1323
+ serviceClient;
1324
+ constructor(config){
1325
+ this.config = config;
1326
+ this.serviceClient = new ServiceClient({
1327
+ baseUrl: config.serviceUrl,
1328
+ apiKey: config.apiKey
1329
+ });
1330
+ }
1331
+ async connect(config) {
1332
+ const databaseName = config.databaseName || 'default';
1333
+ // Ensure database exists
1334
+ const dbInfo = await this.serviceClient.getDatabaseByName(databaseName);
1335
+ if (!dbInfo) // Create database - pass nodeId from config if provided, otherwise server selects
1336
+ await this.serviceClient.createDatabase(databaseName, this.config.nodeId);
1337
+ // Get fresh database info
1338
+ const db = await this.serviceClient.getDatabaseByName(databaseName);
1339
+ if (!db) throw new Error(`Database ${databaseName} not found after creation`);
1340
+ // Create ODBLiteClient for this database
1341
+ const client = new ODBLiteClient({
1342
+ baseUrl: this.config.serviceUrl,
1343
+ apiKey: this.config.apiKey,
1344
+ databaseId: db.hash
1345
+ });
1346
+ return new ODBLiteConnection(client, this.serviceClient, databaseName);
1347
+ }
1348
+ async disconnect(tenantId) {
1349
+ if (tenantId) {
1350
+ const prefix = 'pipeline_tenant_' // TODO: make configurable
1351
+ ;
1352
+ const databaseName = `${prefix}${tenantId}`;
1353
+ try {
1354
+ await this.serviceClient.deleteDatabase(databaseName);
1355
+ } catch (error) {
1356
+ console.warn(`Failed to delete database ${databaseName}:`, error.message);
1357
+ }
1358
+ }
1359
+ }
1360
+ async isHealthy() {
1361
+ try {
1362
+ await this.serviceClient.listDatabases();
1363
+ return true;
1364
+ } catch {
1365
+ return false;
1366
+ }
1367
+ }
1368
+ }
1369
+ /**
1370
+ * Connection implementation for ODB-Lite
1371
+ */ class ODBLiteConnection {
1372
+ client;
1373
+ serviceClient;
1374
+ databaseName;
1375
+ inTransaction = false;
1376
+ constructor(client, serviceClient, databaseName){
1377
+ this.client = client;
1378
+ this.serviceClient = serviceClient;
1379
+ this.databaseName = databaseName;
1380
+ }
1381
+ /**
1382
+ * Template tag query (postgres.js-like)
1383
+ */ async query(strings, ...values) {
1384
+ // ODBLiteClient.sql is a function that returns a Promise
1385
+ const result = await this.client.sql(strings, ...values);
1386
+ return {
1387
+ rows: result.rows,
1388
+ rowsAffected: result.rowsAffected || 0
1389
+ };
1390
+ }
1391
+ /**
1392
+ * Execute SQL with parameters
1393
+ */ async execute(sql, params = []) {
1394
+ try {
1395
+ const result = await this.client.sql.execute(sql, params);
1396
+ return {
1397
+ rows: result.rows,
1398
+ rowsAffected: result.rowsAffected || 0
1399
+ };
1400
+ } catch (error) {
1401
+ throw new Error(`SQL execution failed: ${error.message}`);
1402
+ }
1403
+ }
1404
+ /**
1405
+ * Prepare statement for repeated execution
1406
+ */ prepare(sql) {
1407
+ return {
1408
+ execute: async (params = [])=>this.execute(sql, params),
1409
+ all: async (params = [])=>{
1410
+ const result = await this.execute(sql, params);
1411
+ return result.rows;
1412
+ },
1413
+ get: async (params = [])=>{
1414
+ const result = await this.execute(sql, params);
1415
+ return result.rows[0] || null;
1416
+ }
1417
+ };
1418
+ }
1419
+ /**
1420
+ * Execute function in transaction
1421
+ */ async transaction(fn) {
1422
+ if (this.inTransaction) // Nested transaction - just execute the function
1423
+ return fn(this);
1424
+ this.inTransaction = true;
1425
+ try {
1426
+ await this.execute('BEGIN');
1427
+ const result = await fn(this);
1428
+ await this.execute('COMMIT');
1429
+ return result;
1430
+ } catch (error) {
1431
+ await this.execute('ROLLBACK');
1432
+ throw error;
1433
+ } finally{
1434
+ this.inTransaction = false;
1435
+ }
1436
+ }
1437
+ /**
1438
+ * Close connection
1439
+ */ async close() {
1440
+ // ODBLiteClient connections are stateless HTTP requests
1441
+ // No explicit close needed
1442
+ }
1443
+ }
1444
+ const external_node_sql_parser_namespaceObject = require("node-sql-parser");
1445
+ /**
1446
+ * Parse SQL file into statements, separating PRAGMA from regular SQL
1447
+ * Uses node-sql-parser for robust SQL parsing
1448
+ */ function parseSQL(sqlContent, options = {}) {
1449
+ const separatePragma = options.separatePragma ?? true;
1450
+ // Use node-sql-parser to properly split SQL statements
1451
+ const statements = splitStatementsWithParser(sqlContent);
1452
+ if (!separatePragma) return {
1453
+ pragmaStatements: [],
1454
+ regularStatements: statements
1455
+ };
1456
+ const pragmaStatements = [];
1457
+ const regularStatements = [];
1458
+ for (const statement of statements){
1459
+ const trimmed = statement.trim().toUpperCase();
1460
+ if (trimmed.startsWith('PRAGMA')) pragmaStatements.push(statement);
1461
+ else regularStatements.push(statement);
1462
+ }
1463
+ return {
1464
+ pragmaStatements,
1465
+ regularStatements
1466
+ };
1467
+ }
1468
+ /**
1469
+ * Split SQL content using node-sql-parser (more robust than custom parser)
1470
+ * Falls back to simple split if parser fails
1471
+ */ function splitStatementsWithParser(sqlContent) {
1472
+ try {
1473
+ const parser = new external_node_sql_parser_namespaceObject.Parser();
1474
+ // node-sql-parser supports splitting multiple statements
1475
+ // Try to use it, but have fallback for edge cases
1476
+ const opt = {
1477
+ database: 'sqlite',
1478
+ type: 'table'
1479
+ };
1480
+ // Split by semicolons first to handle parser limitations with very large files
1481
+ const roughStatements = sqlContent.split(/;[\s\n]*/).map((s)=>s.trim()).filter((s)=>s.length > 0);
1482
+ const validStatements = [];
1483
+ for (const stmt of roughStatements){
1484
+ // Add semicolon back
1485
+ const fullStmt = stmt + ';';
1486
+ // Skip comments-only statements
1487
+ if (!stmt.trim().startsWith('--') && '' !== stmt.trim()) // Validate with parser (but don't fail if it can't parse complex statements)
1488
+ try {
1489
+ parser.astify(fullStmt, opt);
1490
+ validStatements.push(fullStmt);
1491
+ } catch (parseError) {
1492
+ // If parser can't handle it, include it anyway if it looks like valid SQL
1493
+ // This handles complex statements like CREATE TRIGGER, CREATE VIEW with subqueries
1494
+ if (stmt.match(/^(CREATE|ALTER|DROP|INSERT|UPDATE|DELETE|SELECT|PRAGMA)/i)) validStatements.push(fullStmt);
1495
+ else console.warn(`Skipping potentially invalid statement: ${stmt.substring(0, 50)}...`);
1496
+ }
1497
+ }
1498
+ return validStatements;
1499
+ } catch (error) {
1500
+ console.warn('node-sql-parser failed, using fallback parser:', error);
1501
+ // Fallback to original simple parser
1502
+ return splitStatements(sqlContent);
1503
+ }
1504
+ }
1505
+ /**
1506
+ * Split SQL content into individual statements
1507
+ * Handles comments, quotes, and semicolons properly
1508
+ */ function splitStatements(sqlContent) {
1509
+ const statements = [];
1510
+ let currentStatement = '';
1511
+ let inQuote = false;
1512
+ let quoteChar = '';
1513
+ const lines = sqlContent.split('\n');
1514
+ for (const line of lines){
1515
+ let processedLine = '';
1516
+ for(let i = 0; i < line.length; i++){
1517
+ const char = line[i];
1518
+ const prevChar = i > 0 ? line[i - 1] : '';
1519
+ const nextChar = i < line.length - 1 ? line[i + 1] : '';
1520
+ // Handle single-line comments
1521
+ if (!inQuote && '-' === char && '-' === nextChar) break; // Skip rest of line
1522
+ // Track quotes
1523
+ if (("'" === char || '"' === char) && '\\' !== prevChar) {
1524
+ if (inQuote) {
1525
+ if (char === quoteChar) {
1526
+ inQuote = false;
1527
+ quoteChar = '';
1528
+ }
1529
+ } else {
1530
+ inQuote = true;
1531
+ quoteChar = char;
1532
+ }
1533
+ }
1534
+ processedLine += char;
1535
+ // Split on semicolon when not in quotes
1536
+ if (';' === char && !inQuote) {
1537
+ currentStatement += processedLine;
1538
+ const stmt = currentStatement.trim();
1539
+ if (stmt) statements.push(stmt);
1540
+ currentStatement = '';
1541
+ processedLine = '';
1542
+ }
1543
+ }
1544
+ if (processedLine.trim()) currentStatement += `${processedLine}\n`;
1545
+ }
1546
+ // Handle remaining statement
1547
+ const finalStmt = currentStatement.trim();
1548
+ if (finalStmt) statements.push(finalStmt);
1549
+ return statements;
1550
+ }
1551
+ /**
1552
+ * Simple split for systems that don't need PRAGMA separation
1553
+ */ function splitSQLStatements(sqlContent) {
1554
+ const { pragmaStatements, regularStatements } = parseSQL(sqlContent);
1555
+ return [
1556
+ ...pragmaStatements,
1557
+ ...regularStatements
1558
+ ];
1559
+ }
1560
+ const external_node_fs_namespaceObject = require("node:fs");
1561
+ var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
1562
+ /**
1563
+ * Database factory for creating and managing multiple databases
1564
+ *
1565
+ * Features:
1566
+ * - Creates multiple databases from single manager instance
1567
+ * - Supports libsql, bun:sqlite, and ODB-Lite backends
1568
+ * - Connection pooling with LRU eviction
1569
+ * - Automatic schema migrations
1570
+ *
1571
+ * @example
1572
+ * ```ts
1573
+ * const dbManager = new DatabaseManager({
1574
+ * backend: 'libsql',
1575
+ * databasePath: './data'
1576
+ * })
1577
+ *
1578
+ * // Create databases
1579
+ * await dbManager.createDatabase('identity', { schemaContent })
1580
+ * await dbManager.createDatabase('analytics', { schemaContent })
1581
+ *
1582
+ * // Get connections
1583
+ * const identityDB = await dbManager.getConnection('identity')
1584
+ * const analyticsDB = await dbManager.getConnection('analytics')
1585
+ * ```
1586
+ */ class DatabaseManager {
1587
+ config;
1588
+ adapter;
1589
+ connections = new Map();
1590
+ // Connection pool management (LRU)
1591
+ connectionTimestamps = new Map();
1592
+ maxConnections;
1593
+ constructor(config){
1594
+ this.config = config;
1595
+ this.maxConnections = config.connectionPoolSize || 10;
1596
+ // Create appropriate adapter based on backend
1597
+ switch(config.backend){
1598
+ case 'bun-sqlite':
1599
+ this.adapter = new BunSQLiteAdapter({
1600
+ databasePath: config.databasePath,
1601
+ create: true
1602
+ });
1603
+ break;
1604
+ case 'libsql':
1605
+ this.adapter = new LibSQLAdapter({
1606
+ url: `file:${config.databasePath}`,
1607
+ ...config.libsql
1608
+ });
1609
+ break;
1610
+ case 'odblite':
1611
+ if (!config.odblite) throw new Error('odblite config required for odblite backend');
1612
+ this.adapter = new ODBLiteAdapter(config.odblite);
1613
+ break;
1614
+ default:
1615
+ throw new Error(`Unknown backend type: ${config.backend}`);
1616
+ }
1617
+ }
1618
+ /**
1619
+ * Create a new database
1620
+ * @param name - Database name (becomes filename or ODB-Lite database name)
1621
+ * @param options - Optional creation options
1622
+ */ async createDatabase(name, options) {
1623
+ console.log(`📦 Creating database: ${name}`);
1624
+ // Build database-specific config
1625
+ const dbConfig = this.getDatabaseConfig(name);
1626
+ // Create connection
1627
+ const conn = await this.adapter.connect(dbConfig);
1628
+ // Run migrations if schema provided
1629
+ if (options?.schemaContent) await this.runMigrations(conn, {
1630
+ schemaContent: options.schemaContent
1631
+ });
1632
+ // Store connection
1633
+ this.connections.set(name, conn);
1634
+ this.connectionTimestamps.set(name, Date.now());
1635
+ // Evict LRU if pool is full
1636
+ if (this.connections.size > this.maxConnections) await this.evictLRU();
1637
+ console.log(`✅ Database created: ${name}`);
1638
+ }
1639
+ /**
1640
+ * Check if a database exists
1641
+ * @param name - Database name
1642
+ * @returns true if database exists, false otherwise
1643
+ *
1644
+ * Note: For local backends (libsql, bun-sqlite), checks file existence.
1645
+ * For ODB-Lite, checks if database is in connection cache.
1646
+ */ async databaseExists(name) {
1647
+ // Check if already in connection cache
1648
+ if (this.connections.has(name)) return true;
1649
+ // For local file-based backends, check if file exists
1650
+ switch(this.config.backend){
1651
+ case 'bun-sqlite':
1652
+ {
1653
+ const dbPath = `${this.config.databasePath}/${name}.db`;
1654
+ return external_node_fs_default().existsSync(dbPath);
1655
+ }
1656
+ case 'libsql':
1657
+ {
1658
+ const dbPath = `${this.config.databasePath}/${name}.db`;
1659
+ return external_node_fs_default().existsSync(dbPath);
1660
+ }
1661
+ case 'odblite':
1662
+ // For ODB-Lite, only return true if in cache
1663
+ // (no way to check remote database existence without connecting)
1664
+ return false;
1665
+ default:
1666
+ return false;
1667
+ }
1668
+ }
1669
+ /**
1670
+ * Get connection to a database
1671
+ * @param name - Database name
1672
+ *
1673
+ * If database exists on disk but not in cache, it will be connected automatically.
1674
+ * If database doesn't exist at all, an error will be thrown.
1675
+ */ async getConnection(name) {
1676
+ // Check if connection exists in cache
1677
+ let conn = this.connections.get(name);
1678
+ if (conn) {
1679
+ this.connectionTimestamps.set(name, Date.now());
1680
+ return conn;
1681
+ }
1682
+ // Check if database exists on disk
1683
+ const exists = await this.databaseExists(name);
1684
+ if (exists) {
1685
+ // Connect to existing database
1686
+ const dbConfig = this.getDatabaseConfig(name);
1687
+ conn = await this.adapter.connect(dbConfig);
1688
+ // Store connection
1689
+ this.connections.set(name, conn);
1690
+ this.connectionTimestamps.set(name, Date.now());
1691
+ // Evict LRU if pool is full
1692
+ if (this.connections.size > this.maxConnections) await this.evictLRU();
1693
+ return conn;
1694
+ }
1695
+ // If database doesn't exist, throw error
1696
+ throw new Error(`Database "${name}" not found. Call createDatabase("${name}", { schemaContent }) first.`);
1697
+ }
1698
+ /**
1699
+ * Delete a database
1700
+ * @param name - Database name
1701
+ */ async deleteDatabase(name) {
1702
+ console.log(`🗑️ Deleting database: ${name}`);
1703
+ // Close connection if exists
1704
+ const conn = this.connections.get(name);
1705
+ if (conn) {
1706
+ await conn.close();
1707
+ this.connections.delete(name);
1708
+ this.connectionTimestamps.delete(name);
1709
+ }
1710
+ // Backend-specific deletion
1711
+ await this.adapter.disconnect(name);
1712
+ console.log(`✅ Database deleted: ${name}`);
1713
+ }
1714
+ /**
1715
+ * Execute SQL content on a database connection
1716
+ * Useful for running migrations or initial schema
1717
+ * @param name - Database name
1718
+ * @param sqlContent - SQL content to execute
1719
+ */ async executeSQLFile(name, sqlContent) {
1720
+ const conn = await this.getConnection(name);
1721
+ await this.runMigrations(conn, {
1722
+ schemaContent: sqlContent
1723
+ });
1724
+ }
1725
+ /**
1726
+ * List all managed databases
1727
+ */ async listDatabases() {
1728
+ return Array.from(this.connections.keys());
1729
+ }
1730
+ /**
1731
+ * Close all connections
1732
+ */ async close() {
1733
+ console.log(`🔌 Closing all database connections...`);
1734
+ for (const [name, conn] of this.connections)await conn.close();
1735
+ this.connections.clear();
1736
+ this.connectionTimestamps.clear();
1737
+ console.log(`✅ All connections closed`);
1738
+ }
1739
+ /**
1740
+ * Run migrations on a connection
1741
+ */ async runMigrations(conn, config) {
1742
+ // Parse SQL statements
1743
+ const { pragmaStatements, regularStatements } = parseSQL(config.schemaContent);
1744
+ // Execute PRAGMA statements first
1745
+ for (const pragma of pragmaStatements)try {
1746
+ await conn.execute(pragma);
1747
+ } catch (error) {
1748
+ // Ignore PRAGMA errors (they might not be supported on all backends)
1749
+ console.log(`⚠️ PRAGMA note: ${error.message}`);
1750
+ }
1751
+ // Execute regular statements
1752
+ for (const statement of regularStatements)try {
1753
+ await conn.execute(statement);
1754
+ } catch (error) {
1755
+ // Ignore "already exists" errors for idempotency
1756
+ if (error.message?.includes('already exists') || error.message?.includes('no such column') || error.message?.includes('no such table')) {
1757
+ console.log(` ⚠️ Skipping: ${error.message}`);
1758
+ continue;
1759
+ }
1760
+ console.error(`❌ Migration failed: ${statement.substring(0, 100)}...`);
1761
+ throw error;
1762
+ }
1763
+ console.log(`✅ Migrations completed (${regularStatements.length} statements)`);
1764
+ }
1765
+ /**
1766
+ * Get database-specific configuration
1767
+ */ getDatabaseConfig(name) {
1768
+ switch(this.config.backend){
1769
+ case 'bun-sqlite':
1770
+ return {
1771
+ databasePath: `${this.config.databasePath}/${name}.db`,
1772
+ create: true
1773
+ };
1774
+ case 'libsql':
1775
+ return {
1776
+ url: `file:${this.config.databasePath}/${name}.db`,
1777
+ ...this.config.libsql
1778
+ };
1779
+ case 'odblite':
1780
+ return {
1781
+ databaseName: `${this.config.databasePath}_${name}`,
1782
+ ...this.config.odblite
1783
+ };
1784
+ default:
1785
+ throw new Error(`Unknown backend: ${this.config.backend}`);
1786
+ }
1787
+ }
1788
+ /**
1789
+ * Evict least recently used connection
1790
+ */ async evictLRU() {
1791
+ let oldestKey = null;
1792
+ let oldestTime = 1 / 0;
1793
+ for (const [key, timestamp] of this.connectionTimestamps)if (timestamp < oldestTime) {
1794
+ oldestTime = timestamp;
1795
+ oldestKey = key;
1796
+ }
1797
+ if (oldestKey) {
1798
+ const conn = this.connections.get(oldestKey);
1799
+ if (conn) await conn.close();
1800
+ this.connections.delete(oldestKey);
1801
+ this.connectionTimestamps.delete(oldestKey);
1802
+ console.log(`♻️ Evicted LRU connection: ${oldestKey}`);
1803
+ }
1804
+ }
1805
+ }
1056
1806
  // Main entry point for ODB Client
1057
1807
  // Core query client exports (postgres.js-like interface)
1058
1808
  // Service management exports (high-level tenant database management)
1809
+ // Database Manager exports (unified multi-backend database abstraction)
1059
1810
  // Export error classes
1060
1811
  // Export static utility functions for easy access
1061
1812
  const { raw: src_raw, identifier: src_identifier, where: src_where, insertValues: src_insertValues, updateSet: src_updateSet, join: src_join } = ODBLiteClient;