@pineliner/odb-client 1.0.0 → 1.0.2

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
@@ -869,8 +890,7 @@ ODBLiteClient.join;
869
890
  try {
870
891
  // Check if database already exists
871
892
  console.log(`🔍 Checking if database exists: ${cacheKey}`);
872
- const databases = await this.listDatabases();
873
- const existing = databases.find((db)=>db.name === cacheKey);
893
+ const existing = await this.getDatabaseByName(cacheKey);
874
894
  if (existing) {
875
895
  console.log(`✅ Database already exists: ${cacheKey} (${existing.hash})`);
876
896
  this.databaseCache.set(cacheKey, existing.hash);
@@ -914,19 +934,20 @@ ODBLiteClient.join;
914
934
  * Create a new database
915
935
  *
916
936
  * @param name - Database name (should be unique)
917
- * @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)
918
938
  * @returns Created database object with hash
919
939
  */ async createDatabase(name, nodeId) {
940
+ const body = {
941
+ name
942
+ };
943
+ if (nodeId) body.nodeId = nodeId;
920
944
  const response = await fetch(`${this.apiUrl}/api/tenant/databases`, {
921
945
  method: 'POST',
922
946
  headers: {
923
947
  Authorization: `Bearer ${this.apiKey}`,
924
948
  'Content-Type': 'application/json'
925
949
  },
926
- body: JSON.stringify({
927
- name,
928
- nodeId
929
- })
950
+ body: JSON.stringify(body)
930
951
  });
931
952
  const result = await response.json();
932
953
  if (!result.success) throw new Error(result.error || 'Failed to create database');
@@ -948,6 +969,22 @@ ODBLiteClient.join;
948
969
  return result.database;
949
970
  }
950
971
  /**
972
+ * Get database details by name
973
+ *
974
+ * @param name - Database name
975
+ * @returns Database object or null if not found
976
+ */ async getDatabaseByName(name) {
977
+ const response = await fetch(`${this.apiUrl}/api/tenant/databases/by-name/${name}`, {
978
+ headers: {
979
+ Authorization: `Bearer ${this.apiKey}`
980
+ }
981
+ });
982
+ if (404 === response.status) return null;
983
+ const result = await response.json();
984
+ if (!result.success) throw new Error(result.error || 'Failed to get database');
985
+ return result.database;
986
+ }
987
+ /**
951
988
  * Delete a database
952
989
  *
953
990
  * @param hash - Database hash to delete
@@ -1038,9 +1075,700 @@ ODBLiteClient.join;
1038
1075
  this.databaseCache.set(cacheKey, hash);
1039
1076
  }
1040
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
+ /**
1445
+ * Parse SQL file into statements, separating PRAGMA from regular SQL
1446
+ * Handles comments, quotes, and semicolons properly
1447
+ */ function parseSQL(sqlContent, options = {}) {
1448
+ const separatePragma = options.separatePragma ?? true;
1449
+ // Split by semicolons (node-sql-parser doesn't handle multiple statements well)
1450
+ const statements = splitStatements(sqlContent);
1451
+ if (!separatePragma) return {
1452
+ pragmaStatements: [],
1453
+ regularStatements: statements
1454
+ };
1455
+ const pragmaStatements = [];
1456
+ const regularStatements = [];
1457
+ for (const statement of statements){
1458
+ const trimmed = statement.trim().toUpperCase();
1459
+ if (trimmed.startsWith('PRAGMA')) pragmaStatements.push(statement);
1460
+ else regularStatements.push(statement);
1461
+ }
1462
+ return {
1463
+ pragmaStatements,
1464
+ regularStatements
1465
+ };
1466
+ }
1467
+ /**
1468
+ * Split SQL content into individual statements
1469
+ * Handles comments, quotes, and semicolons properly
1470
+ */ function splitStatements(sqlContent) {
1471
+ const statements = [];
1472
+ let currentStatement = '';
1473
+ let inQuote = false;
1474
+ let quoteChar = '';
1475
+ const lines = sqlContent.split('\n');
1476
+ for (const line of lines){
1477
+ let processedLine = '';
1478
+ for(let i = 0; i < line.length; i++){
1479
+ const char = line[i];
1480
+ const prevChar = i > 0 ? line[i - 1] : '';
1481
+ const nextChar = i < line.length - 1 ? line[i + 1] : '';
1482
+ // Handle single-line comments
1483
+ if (!inQuote && '-' === char && '-' === nextChar) break; // Skip rest of line
1484
+ // Track quotes
1485
+ if (("'" === char || '"' === char) && '\\' !== prevChar) {
1486
+ if (inQuote) {
1487
+ if (char === quoteChar) {
1488
+ inQuote = false;
1489
+ quoteChar = '';
1490
+ }
1491
+ } else {
1492
+ inQuote = true;
1493
+ quoteChar = char;
1494
+ }
1495
+ }
1496
+ processedLine += char;
1497
+ // Split on semicolon when not in quotes
1498
+ if (';' === char && !inQuote) {
1499
+ currentStatement += processedLine;
1500
+ const stmt = currentStatement.trim();
1501
+ if (stmt) statements.push(stmt);
1502
+ currentStatement = '';
1503
+ processedLine = '';
1504
+ }
1505
+ }
1506
+ if (processedLine.trim()) currentStatement += `${processedLine}\n`;
1507
+ }
1508
+ // Handle remaining statement
1509
+ const finalStmt = currentStatement.trim();
1510
+ if (finalStmt) statements.push(finalStmt);
1511
+ return statements;
1512
+ }
1513
+ /**
1514
+ * Simple split for systems that don't need PRAGMA separation
1515
+ */ function splitSQLStatements(sqlContent) {
1516
+ const { pragmaStatements, regularStatements } = parseSQL(sqlContent);
1517
+ return [
1518
+ ...pragmaStatements,
1519
+ ...regularStatements
1520
+ ];
1521
+ }
1522
+ const external_node_fs_namespaceObject = require("node:fs");
1523
+ var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
1524
+ /**
1525
+ * Database factory for creating and managing multiple databases
1526
+ *
1527
+ * Features:
1528
+ * - Creates multiple databases from single manager instance
1529
+ * - Supports libsql, bun:sqlite, and ODB-Lite backends
1530
+ * - Connection pooling with LRU eviction
1531
+ * - Automatic schema migrations
1532
+ *
1533
+ * @example
1534
+ * ```ts
1535
+ * const dbManager = new DatabaseManager({
1536
+ * backend: 'libsql',
1537
+ * databasePath: './data'
1538
+ * })
1539
+ *
1540
+ * // Create databases
1541
+ * await dbManager.createDatabase('identity', { schemaContent })
1542
+ * await dbManager.createDatabase('analytics', { schemaContent })
1543
+ *
1544
+ * // Get connections
1545
+ * const identityDB = await dbManager.getConnection('identity')
1546
+ * const analyticsDB = await dbManager.getConnection('analytics')
1547
+ * ```
1548
+ */ class DatabaseManager {
1549
+ config;
1550
+ adapter;
1551
+ connections = new Map();
1552
+ // Connection pool management (LRU)
1553
+ connectionTimestamps = new Map();
1554
+ maxConnections;
1555
+ constructor(config){
1556
+ this.config = config;
1557
+ this.maxConnections = config.connectionPoolSize || 10;
1558
+ // Create appropriate adapter based on backend
1559
+ switch(config.backend){
1560
+ case 'bun-sqlite':
1561
+ this.adapter = new BunSQLiteAdapter({
1562
+ databasePath: config.databasePath,
1563
+ create: true
1564
+ });
1565
+ break;
1566
+ case 'libsql':
1567
+ this.adapter = new LibSQLAdapter({
1568
+ url: `file:${config.databasePath}`,
1569
+ ...config.libsql
1570
+ });
1571
+ break;
1572
+ case 'odblite':
1573
+ if (!config.odblite) throw new Error('odblite config required for odblite backend');
1574
+ this.adapter = new ODBLiteAdapter(config.odblite);
1575
+ break;
1576
+ default:
1577
+ throw new Error(`Unknown backend type: ${config.backend}`);
1578
+ }
1579
+ }
1580
+ /**
1581
+ * Create a new database
1582
+ * @param name - Database name (becomes filename or ODB-Lite database name)
1583
+ * @param options - Optional creation options
1584
+ */ async createDatabase(name, options) {
1585
+ console.log(`📦 Creating database: ${name}`);
1586
+ // Build database-specific config
1587
+ const dbConfig = this.getDatabaseConfig(name);
1588
+ // Create connection
1589
+ const conn = await this.adapter.connect(dbConfig);
1590
+ // Run migrations if schema provided
1591
+ if (options?.schemaContent) await this.runMigrations(conn, {
1592
+ schemaContent: options.schemaContent
1593
+ });
1594
+ // Store connection
1595
+ this.connections.set(name, conn);
1596
+ this.connectionTimestamps.set(name, Date.now());
1597
+ // Evict LRU if pool is full
1598
+ if (this.connections.size > this.maxConnections) await this.evictLRU();
1599
+ console.log(`✅ Database created: ${name}`);
1600
+ }
1601
+ /**
1602
+ * Check if a database exists
1603
+ * @param name - Database name
1604
+ * @returns true if database exists, false otherwise
1605
+ *
1606
+ * Note: For local backends (libsql, bun-sqlite), checks file existence.
1607
+ * For ODB-Lite, checks if database is in connection cache.
1608
+ */ async databaseExists(name) {
1609
+ // Check if already in connection cache
1610
+ if (this.connections.has(name)) return true;
1611
+ // For local file-based backends, check if file exists
1612
+ switch(this.config.backend){
1613
+ case 'bun-sqlite':
1614
+ {
1615
+ const dbPath = `${this.config.databasePath}/${name}.db`;
1616
+ return external_node_fs_default().existsSync(dbPath);
1617
+ }
1618
+ case 'libsql':
1619
+ {
1620
+ const dbPath = `${this.config.databasePath}/${name}.db`;
1621
+ return external_node_fs_default().existsSync(dbPath);
1622
+ }
1623
+ case 'odblite':
1624
+ // For ODB-Lite, only return true if in cache
1625
+ // (no way to check remote database existence without connecting)
1626
+ return false;
1627
+ default:
1628
+ return false;
1629
+ }
1630
+ }
1631
+ /**
1632
+ * Get connection to a database
1633
+ * @param name - Database name
1634
+ *
1635
+ * If database exists on disk but not in cache, it will be connected automatically.
1636
+ * If database doesn't exist at all, an error will be thrown.
1637
+ */ async getConnection(name) {
1638
+ // Check if connection exists in cache
1639
+ let conn = this.connections.get(name);
1640
+ if (conn) {
1641
+ this.connectionTimestamps.set(name, Date.now());
1642
+ return conn;
1643
+ }
1644
+ // Check if database exists on disk
1645
+ const exists = await this.databaseExists(name);
1646
+ if (exists) {
1647
+ // Connect to existing database
1648
+ const dbConfig = this.getDatabaseConfig(name);
1649
+ conn = await this.adapter.connect(dbConfig);
1650
+ // Store connection
1651
+ this.connections.set(name, conn);
1652
+ this.connectionTimestamps.set(name, Date.now());
1653
+ // Evict LRU if pool is full
1654
+ if (this.connections.size > this.maxConnections) await this.evictLRU();
1655
+ return conn;
1656
+ }
1657
+ // If database doesn't exist, throw error
1658
+ throw new Error(`Database "${name}" not found. Call createDatabase("${name}", { schemaContent }) first.`);
1659
+ }
1660
+ /**
1661
+ * Delete a database
1662
+ * @param name - Database name
1663
+ */ async deleteDatabase(name) {
1664
+ console.log(`🗑️ Deleting database: ${name}`);
1665
+ // Close connection if exists
1666
+ const conn = this.connections.get(name);
1667
+ if (conn) {
1668
+ await conn.close();
1669
+ this.connections.delete(name);
1670
+ this.connectionTimestamps.delete(name);
1671
+ }
1672
+ // Backend-specific deletion
1673
+ await this.adapter.disconnect(name);
1674
+ console.log(`✅ Database deleted: ${name}`);
1675
+ }
1676
+ /**
1677
+ * Execute SQL content on a database connection
1678
+ * Useful for running migrations or initial schema
1679
+ * @param name - Database name
1680
+ * @param sqlContent - SQL content to execute
1681
+ */ async executeSQLFile(name, sqlContent) {
1682
+ const conn = await this.getConnection(name);
1683
+ await this.runMigrations(conn, {
1684
+ schemaContent: sqlContent
1685
+ });
1686
+ }
1687
+ /**
1688
+ * List all managed databases
1689
+ */ async listDatabases() {
1690
+ return Array.from(this.connections.keys());
1691
+ }
1692
+ /**
1693
+ * Close all connections
1694
+ */ async close() {
1695
+ console.log(`🔌 Closing all database connections...`);
1696
+ for (const [name, conn] of this.connections)await conn.close();
1697
+ this.connections.clear();
1698
+ this.connectionTimestamps.clear();
1699
+ console.log(`✅ All connections closed`);
1700
+ }
1701
+ /**
1702
+ * Run migrations on a connection
1703
+ */ async runMigrations(conn, config) {
1704
+ // Parse SQL statements
1705
+ const { pragmaStatements, regularStatements } = parseSQL(config.schemaContent);
1706
+ // Execute PRAGMA statements first
1707
+ for (const pragma of pragmaStatements)try {
1708
+ await conn.execute(pragma);
1709
+ } catch (error) {
1710
+ // Ignore PRAGMA errors (they might not be supported on all backends)
1711
+ console.log(`⚠️ PRAGMA note: ${error.message}`);
1712
+ }
1713
+ // Execute regular statements
1714
+ for (const statement of regularStatements)try {
1715
+ await conn.execute(statement);
1716
+ } catch (error) {
1717
+ // Ignore "already exists" errors for idempotency
1718
+ if (error.message?.includes('already exists') || error.message?.includes('no such column') || error.message?.includes('no such table')) {
1719
+ console.log(` ⚠️ Skipping: ${error.message}`);
1720
+ continue;
1721
+ }
1722
+ console.error(`❌ Migration failed: ${statement.substring(0, 100)}...`);
1723
+ throw error;
1724
+ }
1725
+ console.log(`✅ Migrations completed (${regularStatements.length} statements)`);
1726
+ }
1727
+ /**
1728
+ * Get database-specific configuration
1729
+ */ getDatabaseConfig(name) {
1730
+ switch(this.config.backend){
1731
+ case 'bun-sqlite':
1732
+ return {
1733
+ databasePath: `${this.config.databasePath}/${name}.db`,
1734
+ create: true
1735
+ };
1736
+ case 'libsql':
1737
+ return {
1738
+ url: `file:${this.config.databasePath}/${name}.db`,
1739
+ ...this.config.libsql
1740
+ };
1741
+ case 'odblite':
1742
+ return {
1743
+ databaseName: `${this.config.databasePath}_${name}`,
1744
+ ...this.config.odblite
1745
+ };
1746
+ default:
1747
+ throw new Error(`Unknown backend: ${this.config.backend}`);
1748
+ }
1749
+ }
1750
+ /**
1751
+ * Evict least recently used connection
1752
+ */ async evictLRU() {
1753
+ let oldestKey = null;
1754
+ let oldestTime = 1 / 0;
1755
+ for (const [key, timestamp] of this.connectionTimestamps)if (timestamp < oldestTime) {
1756
+ oldestTime = timestamp;
1757
+ oldestKey = key;
1758
+ }
1759
+ if (oldestKey) {
1760
+ const conn = this.connections.get(oldestKey);
1761
+ if (conn) await conn.close();
1762
+ this.connections.delete(oldestKey);
1763
+ this.connectionTimestamps.delete(oldestKey);
1764
+ console.log(`♻️ Evicted LRU connection: ${oldestKey}`);
1765
+ }
1766
+ }
1767
+ }
1041
1768
  // Main entry point for ODB Client
1042
1769
  // Core query client exports (postgres.js-like interface)
1043
1770
  // Service management exports (high-level tenant database management)
1771
+ // Database Manager exports (unified multi-backend database abstraction)
1044
1772
  // Export error classes
1045
1773
  // Export static utility functions for easy access
1046
1774
  const { raw: src_raw, identifier: src_identifier, where: src_where, insertValues: src_insertValues, updateSet: src_updateSet, join: src_join } = ODBLiteClient;