@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/database/adapters/bun-sqlite.d.ts +14 -0
- package/dist/database/adapters/bun-sqlite.d.ts.map +1 -0
- package/dist/database/adapters/libsql.d.ts +14 -0
- package/dist/database/adapters/libsql.d.ts.map +1 -0
- package/dist/database/adapters/odblite.d.ts +15 -0
- package/dist/database/adapters/odblite.d.ts.map +1 -0
- package/dist/database/index.d.ts +8 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/manager.d.ts +92 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/sql-parser.d.ts +17 -0
- package/dist/database/sql-parser.d.ts.map +1 -0
- package/dist/database/types.d.ts +106 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/index.cjs +761 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +735 -6
- package/dist/service/service-client.d.ts +2 -2
- package/dist/service/service-client.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/database/adapters/bun-sqlite.ts +170 -0
- package/src/database/adapters/libsql.ts +156 -0
- package/src/database/adapters/odblite.ts +181 -0
- package/src/database/index.ts +32 -0
- package/src/database/manager.ts +336 -0
- package/src/database/sql-parser.ts +168 -0
- package/src/database/types.ts +140 -0
- package/src/index.ts +28 -0
- package/src/service/service-client.ts +8 -3
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/
|
|
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
|
-
|
|
51
|
+
fragment: ()=>/* reexport */ fragment,
|
|
37
52
|
HTTPClient: ()=>/* reexport */ HTTPClient,
|
|
38
53
|
default: ()=>/* reexport */ odblite,
|
|
39
|
-
|
|
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
|
-
|
|
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;
|