@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/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 +740 -12
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +714 -8
- package/dist/service/service-client.d.ts +9 -2
- package/dist/service/service-client.d.ts.map +1 -1
- package/package.json +4 -3
- 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 +112 -0
- package/src/database/types.ts +140 -0
- package/src/index.ts +28 -0
- package/src/service/service-client.ts +34 -5
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
|
|
@@ -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
|
|
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;
|