@pineliner/odb-client 1.0.5 → 1.0.7
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/core/http-client.d.ts.map +1 -1
- package/dist/database/adapters/bun-sqlite.d.ts.map +1 -1
- package/dist/database/adapters/libsql.d.ts.map +1 -1
- package/dist/database/adapters/odblite.d.ts +2 -1
- package/dist/database/adapters/odblite.d.ts.map +1 -1
- package/dist/database/index.d.ts +2 -0
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/manager.d.ts +2 -1
- package/dist/database/manager.d.ts.map +1 -1
- package/dist/database/sql-template.d.ts +432 -0
- package/dist/database/sql-template.d.ts.map +1 -0
- package/dist/database/sql-template.examples.d.ts +28 -0
- package/dist/database/sql-template.examples.d.ts.map +1 -0
- package/dist/database/types.d.ts +10 -1
- package/dist/database/types.d.ts.map +1 -1
- package/dist/index.cjs +1861 -1663
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +785 -653
- package/dist/orm/index.d.ts +228 -0
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/http-client.ts +1 -0
- package/src/database/adapters/bun-sqlite.ts +73 -15
- package/src/database/adapters/libsql.ts +73 -15
- package/src/database/adapters/odblite.ts +95 -26
- package/src/database/index.ts +4 -0
- package/src/database/manager.ts +5 -1
- package/src/database/sql-template.examples.ts +363 -0
- package/src/database/sql-template.ts +660 -0
- package/src/database/types.ts +19 -3
- package/src/index.ts +31 -0
- package/src/orm/index.ts +538 -0
- package/src/types.ts +2 -0
package/dist/index.cjs
CHANGED
|
@@ -1,40 +1,366 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
var __webpack_modules__ = {
|
|
3
|
+
"./src/orm/index.ts": function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
4
|
+
__webpack_require__.r(__webpack_exports__);
|
|
5
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
6
|
+
ORM: ()=>ORM,
|
|
7
|
+
and: ()=>and,
|
|
8
|
+
createORM: ()=>createORM,
|
|
9
|
+
eq: ()=>eq,
|
|
10
|
+
gt: ()=>gt,
|
|
11
|
+
gte: ()=>gte,
|
|
12
|
+
inArray: ()=>inArray,
|
|
13
|
+
isNotNull: ()=>isNotNull,
|
|
14
|
+
isNull: ()=>isNull,
|
|
15
|
+
like: ()=>like,
|
|
16
|
+
lt: ()=>lt,
|
|
17
|
+
lte: ()=>lte,
|
|
18
|
+
ne: ()=>ne,
|
|
19
|
+
or: ()=>or
|
|
20
|
+
});
|
|
21
|
+
function eq(field, value) {
|
|
22
|
+
return {
|
|
23
|
+
sql: `${field} = ?`,
|
|
24
|
+
params: [
|
|
25
|
+
value
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function ne(field, value) {
|
|
30
|
+
return {
|
|
31
|
+
sql: `${field} != ?`,
|
|
32
|
+
params: [
|
|
33
|
+
value
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function gt(field, value) {
|
|
38
|
+
return {
|
|
39
|
+
sql: `${field} > ?`,
|
|
40
|
+
params: [
|
|
41
|
+
value
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function gte(field, value) {
|
|
46
|
+
return {
|
|
47
|
+
sql: `${field} >= ?`,
|
|
48
|
+
params: [
|
|
49
|
+
value
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function lt(field, value) {
|
|
54
|
+
return {
|
|
55
|
+
sql: `${field} < ?`,
|
|
56
|
+
params: [
|
|
57
|
+
value
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function lte(field, value) {
|
|
62
|
+
return {
|
|
63
|
+
sql: `${field} <= ?`,
|
|
64
|
+
params: [
|
|
65
|
+
value
|
|
66
|
+
]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function like(field, pattern) {
|
|
70
|
+
return {
|
|
71
|
+
sql: `${field} LIKE ?`,
|
|
72
|
+
params: [
|
|
73
|
+
pattern
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function inArray(field, values) {
|
|
78
|
+
const placeholders = values.map(()=>'?').join(', ');
|
|
79
|
+
return {
|
|
80
|
+
sql: `${field} IN (${placeholders})`,
|
|
81
|
+
params: values
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function isNull(field) {
|
|
85
|
+
return {
|
|
86
|
+
sql: `${field} IS NULL`,
|
|
87
|
+
params: []
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function isNotNull(field) {
|
|
91
|
+
return {
|
|
92
|
+
sql: `${field} IS NOT NULL`,
|
|
93
|
+
params: []
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function and(...conditions) {
|
|
97
|
+
const sql = conditions.map((c)=>`(${c.sql})`).join(' AND ');
|
|
98
|
+
const params = conditions.flatMap((c)=>c.params);
|
|
99
|
+
return {
|
|
100
|
+
sql,
|
|
101
|
+
params
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function or(...conditions) {
|
|
105
|
+
const sql = conditions.map((c)=>`(${c.sql})`).join(' OR ');
|
|
106
|
+
const params = conditions.flatMap((c)=>c.params);
|
|
107
|
+
return {
|
|
108
|
+
sql,
|
|
109
|
+
params
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
class SelectBuilder {
|
|
113
|
+
db;
|
|
114
|
+
tableName;
|
|
115
|
+
selectFields = [
|
|
116
|
+
'*'
|
|
117
|
+
];
|
|
118
|
+
whereConditions = [];
|
|
119
|
+
orderByField;
|
|
120
|
+
orderByDir = 'ASC';
|
|
121
|
+
limitValue;
|
|
122
|
+
offsetValue;
|
|
123
|
+
constructor(db, tableName){
|
|
124
|
+
this.db = db;
|
|
125
|
+
this.tableName = tableName;
|
|
126
|
+
}
|
|
127
|
+
select(fields = {}) {
|
|
128
|
+
if (0 === Object.keys(fields).length) this.selectFields = [
|
|
129
|
+
'*'
|
|
130
|
+
];
|
|
131
|
+
else this.selectFields = Object.keys(fields);
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
where(condition) {
|
|
135
|
+
this.whereConditions.push(condition);
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
orderBy(field, direction = 'ASC') {
|
|
139
|
+
this.orderByField = field;
|
|
140
|
+
this.orderByDir = direction;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
limit(value) {
|
|
144
|
+
this.limitValue = value;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
offset(value) {
|
|
148
|
+
this.offsetValue = value;
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
async execute() {
|
|
152
|
+
const fields = this.selectFields.join(', ');
|
|
153
|
+
const params = [];
|
|
154
|
+
let sql = `SELECT ${fields} FROM ${this.tableName}`;
|
|
155
|
+
if (this.whereConditions.length > 0) {
|
|
156
|
+
const combinedCondition = 1 === this.whereConditions.length ? this.whereConditions[0] : and(...this.whereConditions);
|
|
157
|
+
sql += ` WHERE ${combinedCondition.sql}`;
|
|
158
|
+
params.push(...combinedCondition.params);
|
|
159
|
+
}
|
|
160
|
+
if (this.orderByField) sql += ` ORDER BY ${this.orderByField} ${this.orderByDir}`;
|
|
161
|
+
if (void 0 !== this.limitValue) {
|
|
162
|
+
sql += " LIMIT ?";
|
|
163
|
+
params.push(this.limitValue);
|
|
164
|
+
}
|
|
165
|
+
if (void 0 !== this.offsetValue) {
|
|
166
|
+
sql += " OFFSET ?";
|
|
167
|
+
params.push(this.offsetValue);
|
|
168
|
+
}
|
|
169
|
+
const result = await this.db.execute(sql, params);
|
|
170
|
+
return result.rows;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
class InsertBuilder {
|
|
174
|
+
db;
|
|
175
|
+
tableName;
|
|
176
|
+
insertValues = {};
|
|
177
|
+
shouldReturn = false;
|
|
178
|
+
constructor(db, tableName){
|
|
179
|
+
this.db = db;
|
|
180
|
+
this.tableName = tableName;
|
|
181
|
+
}
|
|
182
|
+
values(data) {
|
|
183
|
+
this.insertValues = data;
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
returning() {
|
|
187
|
+
this.shouldReturn = true;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
async execute() {
|
|
191
|
+
const fields = Object.keys(this.insertValues);
|
|
192
|
+
const placeholders = fields.map(()=>'?').join(', ');
|
|
193
|
+
const values = fields.map((f)=>this.insertValues[f]);
|
|
194
|
+
let sql = `
|
|
195
|
+
INSERT INTO ${this.tableName} (${fields.join(', ')})
|
|
196
|
+
VALUES (${placeholders})
|
|
197
|
+
`;
|
|
198
|
+
await this.db.execute(sql, values);
|
|
199
|
+
if (this.shouldReturn) {
|
|
200
|
+
const selectSql = `SELECT * FROM ${this.tableName} ORDER BY id DESC LIMIT 1`;
|
|
201
|
+
const result = await this.db.execute(selectSql, []);
|
|
202
|
+
return result.rows;
|
|
203
|
+
}
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
class UpdateBuilder {
|
|
208
|
+
db;
|
|
209
|
+
tableName;
|
|
210
|
+
updateValues = {};
|
|
211
|
+
whereConditions = [];
|
|
212
|
+
shouldReturn = false;
|
|
213
|
+
constructor(db, tableName){
|
|
214
|
+
this.db = db;
|
|
215
|
+
this.tableName = tableName;
|
|
216
|
+
}
|
|
217
|
+
set(data) {
|
|
218
|
+
this.updateValues = data;
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
where(condition) {
|
|
222
|
+
this.whereConditions.push(condition);
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
returning() {
|
|
226
|
+
this.shouldReturn = true;
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
async execute() {
|
|
230
|
+
const fields = Object.keys(this.updateValues);
|
|
231
|
+
const setClause = fields.map((f)=>`${f} = ?`).join(', ');
|
|
232
|
+
const values = fields.map((f)=>this.updateValues[f]);
|
|
233
|
+
let sql = `UPDATE ${this.tableName} SET ${setClause}`;
|
|
234
|
+
const params = [
|
|
235
|
+
...values
|
|
236
|
+
];
|
|
237
|
+
if (this.whereConditions.length > 0) {
|
|
238
|
+
const combinedCondition = 1 === this.whereConditions.length ? this.whereConditions[0] : and(...this.whereConditions);
|
|
239
|
+
sql += ` WHERE ${combinedCondition.sql}`;
|
|
240
|
+
params.push(...combinedCondition.params);
|
|
241
|
+
}
|
|
242
|
+
await this.db.execute(sql, params);
|
|
243
|
+
if (this.shouldReturn && this.whereConditions.length > 0) {
|
|
244
|
+
const combinedCondition = 1 === this.whereConditions.length ? this.whereConditions[0] : and(...this.whereConditions);
|
|
245
|
+
const selectSql = `SELECT * FROM ${this.tableName} WHERE ${combinedCondition.sql}`;
|
|
246
|
+
const result = await this.db.execute(selectSql, combinedCondition.params);
|
|
247
|
+
return result.rows;
|
|
248
|
+
}
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
class DeleteBuilder {
|
|
253
|
+
db;
|
|
254
|
+
tableName;
|
|
255
|
+
whereConditions = [];
|
|
256
|
+
constructor(db, tableName){
|
|
257
|
+
this.db = db;
|
|
258
|
+
this.tableName = tableName;
|
|
259
|
+
}
|
|
260
|
+
where(condition) {
|
|
261
|
+
this.whereConditions.push(condition);
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
async execute() {
|
|
265
|
+
let sql = `DELETE FROM ${this.tableName}`;
|
|
266
|
+
const params = [];
|
|
267
|
+
if (this.whereConditions.length > 0) {
|
|
268
|
+
const combinedCondition = 1 === this.whereConditions.length ? this.whereConditions[0] : and(...this.whereConditions);
|
|
269
|
+
sql += ` WHERE ${combinedCondition.sql}`;
|
|
270
|
+
params.push(...combinedCondition.params);
|
|
271
|
+
}
|
|
272
|
+
await this.db.execute(sql, params);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
class TableQueryBuilder {
|
|
276
|
+
db;
|
|
277
|
+
tableName;
|
|
278
|
+
constructor(db, tableName){
|
|
279
|
+
this.db = db;
|
|
280
|
+
this.tableName = tableName;
|
|
281
|
+
}
|
|
282
|
+
select(fields) {
|
|
283
|
+
const builder = new SelectBuilder(this.db, this.tableName);
|
|
284
|
+
if (fields) builder.select(fields);
|
|
285
|
+
return builder;
|
|
286
|
+
}
|
|
287
|
+
insert() {
|
|
288
|
+
return new InsertBuilder(this.db, this.tableName);
|
|
289
|
+
}
|
|
290
|
+
update() {
|
|
291
|
+
return new UpdateBuilder(this.db, this.tableName);
|
|
292
|
+
}
|
|
293
|
+
delete() {
|
|
294
|
+
return new DeleteBuilder(this.db, this.tableName);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
class ORM {
|
|
298
|
+
db;
|
|
299
|
+
constructor(db){
|
|
300
|
+
this.db = db;
|
|
301
|
+
}
|
|
302
|
+
table(tableName) {
|
|
303
|
+
return new TableQueryBuilder(this.db, tableName);
|
|
304
|
+
}
|
|
305
|
+
select(fields) {
|
|
306
|
+
return {
|
|
307
|
+
from: (tableName)=>{
|
|
308
|
+
const builder = new SelectBuilder(this.db, tableName);
|
|
309
|
+
if (fields) builder.select(fields);
|
|
310
|
+
return builder;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
insert(tableName) {
|
|
315
|
+
return new InsertBuilder(this.db, tableName);
|
|
316
|
+
}
|
|
317
|
+
update(tableName) {
|
|
318
|
+
return new UpdateBuilder(this.db, tableName);
|
|
319
|
+
}
|
|
320
|
+
delete(tableName) {
|
|
321
|
+
return new DeleteBuilder(this.db, tableName);
|
|
322
|
+
}
|
|
323
|
+
execute(sql, params) {
|
|
324
|
+
return this.db.execute(sql, params);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function createORM(db) {
|
|
328
|
+
return new ORM(db);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var __webpack_module_cache__ = {};
|
|
333
|
+
function __webpack_require__(moduleId) {
|
|
334
|
+
var cachedModule = __webpack_module_cache__[moduleId];
|
|
335
|
+
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
336
|
+
var module = __webpack_module_cache__[moduleId] = {
|
|
337
|
+
exports: {}
|
|
338
|
+
};
|
|
339
|
+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
340
|
+
return module.exports;
|
|
341
|
+
}
|
|
5
342
|
(()=>{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var getter = module && module.__esModule ? function() {
|
|
9
|
-
return module['default'];
|
|
10
|
-
} : function() {
|
|
11
|
-
return module;
|
|
12
|
-
};
|
|
343
|
+
__webpack_require__.n = (module)=>{
|
|
344
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
13
345
|
__webpack_require__.d(getter, {
|
|
14
346
|
a: getter
|
|
15
347
|
});
|
|
16
348
|
return getter;
|
|
17
349
|
};
|
|
18
350
|
})();
|
|
19
|
-
// webpack/runtime/define_property_getters
|
|
20
351
|
(()=>{
|
|
21
|
-
__webpack_require__.d =
|
|
352
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
22
353
|
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
23
354
|
enumerable: true,
|
|
24
355
|
get: definition[key]
|
|
25
356
|
});
|
|
26
357
|
};
|
|
27
358
|
})();
|
|
28
|
-
// webpack/runtime/has_own_property
|
|
29
359
|
(()=>{
|
|
30
|
-
__webpack_require__.o =
|
|
31
|
-
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
32
|
-
};
|
|
360
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
33
361
|
})();
|
|
34
|
-
// webpack/runtime/make_namespace_object
|
|
35
362
|
(()=>{
|
|
36
|
-
|
|
37
|
-
__webpack_require__.r = function(exports1) {
|
|
363
|
+
__webpack_require__.r = (exports1)=>{
|
|
38
364
|
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
39
365
|
value: 'Module'
|
|
40
366
|
});
|
|
@@ -43,117 +369,144 @@ var __webpack_require__ = {};
|
|
|
43
369
|
});
|
|
44
370
|
};
|
|
45
371
|
})();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
__webpack_require__.r(__webpack_exports__);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
372
|
+
var __webpack_exports__ = {};
|
|
373
|
+
(()=>{
|
|
374
|
+
__webpack_require__.r(__webpack_exports__);
|
|
375
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
376
|
+
insertValues: ()=>src_insertValues,
|
|
377
|
+
updateSet: ()=>src_updateSet,
|
|
378
|
+
gte: ()=>orm.gte,
|
|
379
|
+
ODBLiteClient: ()=>ODBLiteClient,
|
|
380
|
+
QueryError: ()=>types_QueryError,
|
|
381
|
+
sql: ()=>sql_parser_sql,
|
|
382
|
+
sqlFragment: ()=>sql_template_fragment,
|
|
383
|
+
ODBLiteError: ()=>ODBLiteError,
|
|
384
|
+
ORM: ()=>orm.ORM,
|
|
385
|
+
ConnectionError: ()=>ConnectionError,
|
|
386
|
+
join: ()=>src_join,
|
|
387
|
+
convertTemplateToQuery: ()=>convertTemplateToQuery,
|
|
388
|
+
HTTPClient: ()=>HTTPClient,
|
|
389
|
+
BunSQLiteAdapter: ()=>BunSQLiteAdapter,
|
|
390
|
+
SQLParser: ()=>sql_parser_SQLParser,
|
|
391
|
+
default: ()=>odblite,
|
|
392
|
+
gt: ()=>orm.gt,
|
|
393
|
+
eq: ()=>orm.eq,
|
|
394
|
+
identifier: ()=>src_identifier,
|
|
395
|
+
lte: ()=>orm.lte,
|
|
396
|
+
ne: ()=>orm.ne,
|
|
397
|
+
parseSQL: ()=>parseSQL,
|
|
398
|
+
splitSQLStatements: ()=>splitSQLStatements,
|
|
399
|
+
where: ()=>src_where,
|
|
400
|
+
LibSQLAdapter: ()=>LibSQLAdapter,
|
|
401
|
+
lt: ()=>orm.lt,
|
|
402
|
+
fragment: ()=>fragment,
|
|
403
|
+
isNull: ()=>orm.isNull,
|
|
404
|
+
DatabaseManager: ()=>DatabaseManager,
|
|
405
|
+
or: ()=>orm.or,
|
|
406
|
+
ODBLiteTransaction: ()=>ODBLiteTransaction,
|
|
407
|
+
ServiceClient: ()=>ServiceClient,
|
|
408
|
+
and: ()=>orm.and,
|
|
409
|
+
odblite: ()=>odblite,
|
|
410
|
+
rawSQL: ()=>sql_template_raw,
|
|
411
|
+
set: ()=>set,
|
|
412
|
+
sqlJoin: ()=>sql_template_join,
|
|
413
|
+
createORM: ()=>orm.createORM,
|
|
414
|
+
empty: ()=>empty,
|
|
415
|
+
sqlTemplate: ()=>sql_template_sql,
|
|
416
|
+
SimpleTransaction: ()=>SimpleTransaction,
|
|
417
|
+
inArray: ()=>orm.inArray,
|
|
418
|
+
sqlWhere: ()=>sql_template_where,
|
|
419
|
+
ODBLiteAdapter: ()=>ODBLiteAdapter,
|
|
420
|
+
isNotNull: ()=>orm.isNotNull,
|
|
421
|
+
like: ()=>orm.like,
|
|
422
|
+
raw: ()=>src_raw
|
|
423
|
+
});
|
|
424
|
+
class ODBLiteError extends Error {
|
|
425
|
+
code;
|
|
426
|
+
query;
|
|
427
|
+
params;
|
|
428
|
+
constructor(message, code, query, params){
|
|
429
|
+
super(message), this.code = code, this.query = query, this.params = params;
|
|
430
|
+
this.name = 'ODBLiteError';
|
|
431
|
+
}
|
|
93
432
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
433
|
+
class ConnectionError extends ODBLiteError {
|
|
434
|
+
originalError;
|
|
435
|
+
constructor(message, originalError){
|
|
436
|
+
super(message, 'CONNECTION_ERROR'), this.originalError = originalError;
|
|
437
|
+
this.name = 'ConnectionError';
|
|
438
|
+
}
|
|
100
439
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.config = {
|
|
108
|
-
timeout: 30000,
|
|
109
|
-
retries: 3,
|
|
110
|
-
...config
|
|
111
|
-
};
|
|
112
|
-
// Ensure baseUrl doesn't end with slash
|
|
113
|
-
if ('string' == typeof this.config.baseUrl) this.config.baseUrl = this.config.baseUrl.replace(/\/$/, '');
|
|
440
|
+
class types_QueryError extends ODBLiteError {
|
|
441
|
+
originalError;
|
|
442
|
+
constructor(message, query, params, originalError){
|
|
443
|
+
super(message, 'QUERY_ERROR', query, params), this.originalError = originalError;
|
|
444
|
+
this.name = 'QueryError';
|
|
445
|
+
}
|
|
114
446
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
params
|
|
123
|
-
};
|
|
124
|
-
try {
|
|
125
|
-
const response = await this.makeRequest(url, {
|
|
126
|
-
method: 'POST',
|
|
127
|
-
headers: {
|
|
128
|
-
'Content-Type': 'application/json',
|
|
129
|
-
Authorization: `Bearer ${this.config.apiKey}`
|
|
130
|
-
},
|
|
131
|
-
body: JSON.stringify(body)
|
|
132
|
-
});
|
|
133
|
-
const data = await response.json();
|
|
134
|
-
if (!data.success) throw new types_QueryError(data.error || 'Query failed', sql, params);
|
|
135
|
-
// Handle nested data structure: { success: true, data: { rows: [...] } }
|
|
136
|
-
let rows = [];
|
|
137
|
-
if (data.data && 'object' == typeof data.data && 'rows' in data.data) rows = data.data.rows;
|
|
138
|
-
else if (Array.isArray(data.data)) rows = data.data;
|
|
139
|
-
else if (Array.isArray(data.rows)) rows = data.rows;
|
|
140
|
-
return {
|
|
141
|
-
rows: rows,
|
|
142
|
-
rowsAffected: data.rowsAffected || data.data?.rowsAffected || 0,
|
|
143
|
-
executionTime: data.executionTime || data.data?.executionTime || 0,
|
|
144
|
-
databaseName: data.databaseName || data.data?.databaseName
|
|
447
|
+
class HTTPClient {
|
|
448
|
+
config;
|
|
449
|
+
constructor(config){
|
|
450
|
+
this.config = {
|
|
451
|
+
timeout: 30000,
|
|
452
|
+
retries: 3,
|
|
453
|
+
...config
|
|
145
454
|
};
|
|
146
|
-
|
|
147
|
-
if (error instanceof types_QueryError || error instanceof ConnectionError) throw error;
|
|
148
|
-
throw new types_QueryError(error instanceof Error ? error.message : 'Unknown error occurred', sql, params, error instanceof Error ? error : void 0);
|
|
455
|
+
if ('string' == typeof this.config.baseUrl) this.config.baseUrl = this.config.baseUrl.replace(/\/$/, '');
|
|
149
456
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
457
|
+
async query(sql, params = []) {
|
|
458
|
+
if (!this.config.databaseId) throw new ConnectionError('No database ID configured. Use setDatabase() first.');
|
|
459
|
+
const url = `${this.config.baseUrl}/query/${this.config.databaseId}`;
|
|
460
|
+
const body = {
|
|
461
|
+
sql,
|
|
462
|
+
params
|
|
463
|
+
};
|
|
464
|
+
try {
|
|
465
|
+
const response = await this.makeRequest(url, {
|
|
466
|
+
method: 'POST',
|
|
467
|
+
headers: {
|
|
468
|
+
'Content-Type': 'application/json',
|
|
469
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
470
|
+
},
|
|
471
|
+
body: JSON.stringify(body)
|
|
472
|
+
});
|
|
473
|
+
const data = await response.json();
|
|
474
|
+
if (!data.success) throw new types_QueryError(data.error || 'Query failed', sql, params);
|
|
475
|
+
let rows = [];
|
|
476
|
+
if (data.data && 'object' == typeof data.data && 'rows' in data.data) rows = data.data.rows;
|
|
477
|
+
else if (Array.isArray(data.data)) rows = data.data;
|
|
478
|
+
else if (Array.isArray(data.rows)) rows = data.rows;
|
|
479
|
+
return {
|
|
480
|
+
rows: rows,
|
|
481
|
+
rowsAffected: data.rowsAffected || data.data?.rowsAffected || 0,
|
|
482
|
+
lastInsertRowid: data.lastInsertRowid || data.data?.lastInsertRowid,
|
|
483
|
+
executionTime: data.executionTime || data.data?.executionTime || 0,
|
|
484
|
+
databaseName: data.databaseName || data.data?.databaseName
|
|
485
|
+
};
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (error instanceof types_QueryError || error instanceof ConnectionError) throw error;
|
|
488
|
+
throw new types_QueryError(error instanceof Error ? error.message : 'Unknown error occurred', sql, params, error instanceof Error ? error : void 0);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async ping() {
|
|
492
|
+
if (!this.config.databaseId) return false;
|
|
493
|
+
try {
|
|
494
|
+
const url = `${this.config.baseUrl}/api/db/${this.config.databaseId}/health`;
|
|
495
|
+
const response = await this.makeRequest(url, {
|
|
496
|
+
method: 'GET',
|
|
497
|
+
headers: {
|
|
498
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
const data = await response.json();
|
|
502
|
+
return 'healthy' === data.status;
|
|
503
|
+
} catch {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async getDatabaseInfo() {
|
|
508
|
+
if (!this.config.databaseId) throw new ConnectionError('No database ID configured');
|
|
509
|
+
const url = `${this.config.baseUrl}/query/${this.config.databaseId}`;
|
|
157
510
|
const response = await this.makeRequest(url, {
|
|
158
511
|
method: 'GET',
|
|
159
512
|
headers: {
|
|
@@ -161,1390 +514,1233 @@ class types_QueryError extends ODBLiteError {
|
|
|
161
514
|
}
|
|
162
515
|
});
|
|
163
516
|
const data = await response.json();
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return false;
|
|
517
|
+
if (!data.success) throw new ConnectionError(data.error || 'Failed to get database info');
|
|
518
|
+
return data;
|
|
167
519
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
520
|
+
setDatabase(databaseId) {
|
|
521
|
+
this.config.databaseId = databaseId;
|
|
522
|
+
}
|
|
523
|
+
async makeRequest(url, options) {
|
|
524
|
+
let lastError;
|
|
525
|
+
for(let attempt = 1; attempt <= (this.config.retries || 3); attempt++)try {
|
|
526
|
+
const controller = new AbortController();
|
|
527
|
+
const timeoutId = setTimeout(()=>controller.abort(), this.config.timeout);
|
|
528
|
+
const response = await fetch(url, {
|
|
529
|
+
...options,
|
|
530
|
+
signal: controller.signal
|
|
531
|
+
});
|
|
532
|
+
clearTimeout(timeoutId);
|
|
533
|
+
if (!response.ok) {
|
|
534
|
+
const errorText = await response.text();
|
|
535
|
+
let errorData;
|
|
536
|
+
try {
|
|
537
|
+
errorData = JSON.parse(errorText);
|
|
538
|
+
} catch {
|
|
539
|
+
errorData = {
|
|
540
|
+
error: errorText
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
throw new ConnectionError(`HTTP ${response.status}: ${errorData.error || response.statusText}`);
|
|
544
|
+
}
|
|
545
|
+
return response;
|
|
546
|
+
} catch (error) {
|
|
547
|
+
lastError = error instanceof Error ? error : new Error('Unknown error');
|
|
548
|
+
if (error instanceof ConnectionError && error.message.includes('HTTP 4')) throw error;
|
|
549
|
+
if (attempt < (this.config.retries || 3)) {
|
|
550
|
+
const delay = Math.min(1000 * 2 ** (attempt - 1), 10000);
|
|
551
|
+
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
552
|
+
}
|
|
178
553
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
* Set the database ID for subsequent queries
|
|
186
|
-
*/ setDatabase(databaseId) {
|
|
187
|
-
this.config.databaseId = databaseId;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Make HTTP request with retry logic
|
|
191
|
-
*/ async makeRequest(url, options) {
|
|
192
|
-
let lastError;
|
|
193
|
-
for(let attempt = 1; attempt <= (this.config.retries || 3); attempt++)try {
|
|
194
|
-
const controller = new AbortController();
|
|
195
|
-
const timeoutId = setTimeout(()=>controller.abort(), this.config.timeout);
|
|
196
|
-
const response = await fetch(url, {
|
|
197
|
-
...options,
|
|
198
|
-
signal: controller.signal
|
|
554
|
+
throw new ConnectionError(`Failed after ${this.config.retries} attempts: ${lastError?.message}`, lastError);
|
|
555
|
+
}
|
|
556
|
+
configure(updates) {
|
|
557
|
+
return new HTTPClient({
|
|
558
|
+
...this.config,
|
|
559
|
+
...updates
|
|
199
560
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
561
|
+
}
|
|
562
|
+
getConfig() {
|
|
563
|
+
return {
|
|
564
|
+
...this.config
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
class sql_parser_SQLParser {
|
|
569
|
+
static parse(sql, values) {
|
|
570
|
+
if (Array.isArray(sql) && ('raw' in sql || 'string' == typeof sql[0] && void 0 !== values)) return this.parseTemplateString(sql, values || []);
|
|
571
|
+
return this.parseContextualInput(sql, values);
|
|
572
|
+
}
|
|
573
|
+
static parseContextualInput(input, values, context) {
|
|
574
|
+
if (Array.isArray(input)) {
|
|
575
|
+
if (input.length > 0 && input[0] && 'object' == typeof input[0] && input[0].constructor === Object) {
|
|
576
|
+
const insertResult = this.insertValues(input);
|
|
577
|
+
return {
|
|
578
|
+
sql: insertResult.text,
|
|
579
|
+
params: insertResult.values
|
|
209
580
|
};
|
|
210
581
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// Don't retry on client errors (4xx)
|
|
217
|
-
if (error instanceof ConnectionError && error.message.includes('HTTP 4')) throw error;
|
|
218
|
-
// Wait before retry (exponential backoff)
|
|
219
|
-
if (attempt < (this.config.retries || 3)) {
|
|
220
|
-
const delay = Math.min(1000 * 2 ** (attempt - 1), 10000);
|
|
221
|
-
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
582
|
+
const placeholders = input.map(()=>'?').join(', ');
|
|
583
|
+
return {
|
|
584
|
+
sql: `(${placeholders})`,
|
|
585
|
+
params: input.map((v)=>this.convertValue(v))
|
|
586
|
+
};
|
|
222
587
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* SQL template string parser that converts postgres.js-style template strings
|
|
244
|
-
* into LibSQL-compatible parameterized queries
|
|
245
|
-
*/ class sql_parser_SQLParser {
|
|
246
|
-
/**
|
|
247
|
-
* Parse input that can be template strings, objects, or arrays
|
|
248
|
-
* Context-aware like postgres.js
|
|
249
|
-
*/ static parse(sql, values) {
|
|
250
|
-
// Handle template string arrays (have the raw property or look like template strings)
|
|
251
|
-
if (Array.isArray(sql) && ('raw' in sql || 'string' == typeof sql[0] && void 0 !== values)) return this.parseTemplateString(sql, values || []);
|
|
252
|
-
// Handle direct object/array inputs (context detection)
|
|
253
|
-
return this.parseContextualInput(sql, values);
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Parse contextual input (objects, arrays, etc.)
|
|
257
|
-
* Uses heuristics to detect the intended context
|
|
258
|
-
*/ static parseContextualInput(input, values, context) {
|
|
259
|
-
if (Array.isArray(input)) {
|
|
260
|
-
// Check if this is an array of objects (for INSERT VALUES)
|
|
261
|
-
if (input.length > 0 && input[0] && 'object' == typeof input[0] && input[0].constructor === Object) {
|
|
588
|
+
if (input && 'object' == typeof input && input.constructor === Object) {
|
|
589
|
+
const entries = Object.entries(input).filter(([, value])=>void 0 !== value);
|
|
590
|
+
if (0 === entries.length) return {
|
|
591
|
+
sql: '',
|
|
592
|
+
params: []
|
|
593
|
+
};
|
|
594
|
+
const hasNullValues = entries.some(([, value])=>null === value);
|
|
595
|
+
const hasArrayValues = entries.some(([, value])=>Array.isArray(value));
|
|
596
|
+
const hasComplexValues = entries.some(([, value])=>null !== value && 'object' == typeof value && !Array.isArray(value));
|
|
597
|
+
if (hasNullValues || hasArrayValues || hasComplexValues) {
|
|
598
|
+
const whereResult = this.where(input);
|
|
599
|
+
return {
|
|
600
|
+
sql: whereResult.text,
|
|
601
|
+
params: whereResult.values
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (entries.length <= 3 && !Array.isArray(input)) return this.buildSetClause(input);
|
|
262
605
|
const insertResult = this.insertValues(input);
|
|
263
606
|
return {
|
|
264
607
|
sql: insertResult.text,
|
|
265
608
|
params: insertResult.values
|
|
266
609
|
};
|
|
267
610
|
}
|
|
268
|
-
// Array of primitives - assume it's for IN clause
|
|
269
|
-
const placeholders = input.map(()=>'?').join(', ');
|
|
270
611
|
return {
|
|
271
|
-
sql:
|
|
272
|
-
params:
|
|
612
|
+
sql: '?',
|
|
613
|
+
params: [
|
|
614
|
+
this.convertValue(input)
|
|
615
|
+
]
|
|
273
616
|
};
|
|
274
617
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const entries = Object.entries(input).filter(([, value])=>void 0 !== value);
|
|
618
|
+
static buildSetClause(data) {
|
|
619
|
+
const entries = Object.entries(data).filter(([, value])=>void 0 !== value);
|
|
278
620
|
if (0 === entries.length) return {
|
|
279
621
|
sql: '',
|
|
280
622
|
params: []
|
|
281
623
|
};
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
624
|
+
const setClauses = entries.map(([key])=>`${this.escapeIdentifier(key)} = ?`).join(', ');
|
|
625
|
+
const values = entries.map(([, value])=>this.convertValue(value));
|
|
626
|
+
return {
|
|
627
|
+
sql: setClauses,
|
|
628
|
+
params: values
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
static parseTemplateString(sql, values) {
|
|
632
|
+
const fragments = [
|
|
633
|
+
...sql
|
|
634
|
+
];
|
|
635
|
+
const params = [];
|
|
636
|
+
let query = '';
|
|
637
|
+
for(let i = 0; i < fragments.length; i++){
|
|
638
|
+
query += fragments[i];
|
|
639
|
+
if (i < values.length) {
|
|
640
|
+
const value = values[i];
|
|
641
|
+
if (Array.isArray(value)) {
|
|
642
|
+
const placeholders = value.map(()=>"?").join(', ');
|
|
643
|
+
query += `(${placeholders})`;
|
|
644
|
+
params.push(...value.map((v)=>this.convertValue(v)));
|
|
645
|
+
} else if (value && 'object' == typeof value && value.constructor === Object) {
|
|
646
|
+
const contextResult = this.parseContextualInput(value);
|
|
647
|
+
query += contextResult.sql;
|
|
648
|
+
params.push(...contextResult.params);
|
|
649
|
+
} else if ('string' == typeof value && value.startsWith('__RAW__')) query += value.slice(7);
|
|
650
|
+
else {
|
|
651
|
+
query += '?';
|
|
652
|
+
params.push(this.convertValue(value));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
296
655
|
}
|
|
297
|
-
// For simple objects with primitive values, we need to guess context
|
|
298
|
-
// This is inherently ambiguous - could be SET or VALUES
|
|
299
|
-
// We'll default to a flexible format that works for both
|
|
300
|
-
// If single object, more likely to be SET clause
|
|
301
|
-
if (entries.length <= 3 && !Array.isArray(input)) // Return SET format by default
|
|
302
|
-
return this.buildSetClause(input);
|
|
303
|
-
// For larger objects or arrays of objects, likely INSERT VALUES
|
|
304
|
-
const insertResult = this.insertValues(input);
|
|
305
656
|
return {
|
|
306
|
-
sql:
|
|
307
|
-
params
|
|
657
|
+
sql: query.trim(),
|
|
658
|
+
params
|
|
308
659
|
};
|
|
309
660
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
sql
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
661
|
+
static raw(text) {
|
|
662
|
+
return `__RAW__${text}`;
|
|
663
|
+
}
|
|
664
|
+
static identifier(name) {
|
|
665
|
+
return this.escapeIdentifier(name);
|
|
666
|
+
}
|
|
667
|
+
static escapeIdentifier(identifier) {
|
|
668
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
669
|
+
}
|
|
670
|
+
static convertValue(value) {
|
|
671
|
+
if (null == value) return null;
|
|
672
|
+
if (value instanceof Date) return value.toISOString();
|
|
673
|
+
if (value instanceof Buffer) return new Uint8Array(value);
|
|
674
|
+
if ('boolean' == typeof value) return value ? 1 : 0;
|
|
675
|
+
if ('bigint' == typeof value) return value.toString();
|
|
676
|
+
if ('object' == typeof value) return JSON.stringify(value);
|
|
677
|
+
return value;
|
|
678
|
+
}
|
|
679
|
+
static fragment(sql, ...values) {
|
|
680
|
+
const parsed = this.parse(sql, values);
|
|
681
|
+
return {
|
|
682
|
+
text: parsed.sql,
|
|
683
|
+
values: parsed.params
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
static join(fragments, separator = ' ') {
|
|
687
|
+
const text = fragments.map((f)=>f.text).join(separator);
|
|
688
|
+
const values = fragments.flatMap((f)=>f.values);
|
|
689
|
+
return {
|
|
690
|
+
text,
|
|
691
|
+
values
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
static where(conditions) {
|
|
695
|
+
const entries = Object.entries(conditions).filter(([, value])=>void 0 !== value);
|
|
696
|
+
if (0 === entries.length) return {
|
|
697
|
+
text: '',
|
|
698
|
+
values: []
|
|
699
|
+
};
|
|
700
|
+
const clauses = entries.map(([key, value])=>{
|
|
701
|
+
if (null === value) return `${this.escapeIdentifier(key)} IS NULL`;
|
|
346
702
|
if (Array.isArray(value)) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
query += `(${placeholders})`;
|
|
350
|
-
params.push(...value.map((v)=>this.convertValue(v)));
|
|
351
|
-
} else if (value && 'object' == typeof value && value.constructor === Object) {
|
|
352
|
-
// Use context detection for objects
|
|
353
|
-
const contextResult = this.parseContextualInput(value);
|
|
354
|
-
query += contextResult.sql;
|
|
355
|
-
params.push(...contextResult.params);
|
|
356
|
-
} else if ('string' == typeof value && value.startsWith('__RAW__')) // Handle raw SQL: sql`SELECT * FROM ${raw('users')}`
|
|
357
|
-
query += value.slice(7); // Remove __RAW__ prefix
|
|
358
|
-
else {
|
|
359
|
-
// Regular parameter
|
|
360
|
-
query += '?';
|
|
361
|
-
params.push(this.convertValue(value));
|
|
703
|
+
const placeholders = value.map(()=>'?').join(', ');
|
|
704
|
+
return `${this.escapeIdentifier(key)} IN (${placeholders})`;
|
|
362
705
|
}
|
|
363
|
-
|
|
706
|
+
return `${this.escapeIdentifier(key)} = ?`;
|
|
707
|
+
});
|
|
708
|
+
const values = entries.flatMap(([, value])=>{
|
|
709
|
+
if (null === value) return [];
|
|
710
|
+
if (Array.isArray(value)) return value.map((v)=>this.convertValue(v));
|
|
711
|
+
return [
|
|
712
|
+
this.convertValue(value)
|
|
713
|
+
];
|
|
714
|
+
});
|
|
715
|
+
return {
|
|
716
|
+
text: clauses.join(' AND '),
|
|
717
|
+
values
|
|
718
|
+
};
|
|
364
719
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Create a raw SQL fragment (not parameterized)
|
|
372
|
-
*/ static raw(text) {
|
|
373
|
-
return `__RAW__${text}`;
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Create an identifier (table name, column name, etc.)
|
|
377
|
-
*/ static identifier(name) {
|
|
378
|
-
return this.escapeIdentifier(name);
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Escape SQL identifiers (table names, column names)
|
|
382
|
-
*/ static escapeIdentifier(identifier) {
|
|
383
|
-
// SQLite uses double quotes for identifiers
|
|
384
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Convert JavaScript values to LibSQL-compatible values
|
|
388
|
-
*/ static convertValue(value) {
|
|
389
|
-
if (null == value) return null;
|
|
390
|
-
if (value instanceof Date) // Convert Date to ISO string for SQLite
|
|
391
|
-
return value.toISOString();
|
|
392
|
-
if (value instanceof Buffer) // Convert Buffer to Uint8Array for LibSQL
|
|
393
|
-
return new Uint8Array(value);
|
|
394
|
-
if ('boolean' == typeof value) // SQLite uses 0/1 for booleans
|
|
395
|
-
return value ? 1 : 0;
|
|
396
|
-
if ('bigint' == typeof value) // Convert BigInt to string to avoid precision loss
|
|
397
|
-
return value.toString();
|
|
398
|
-
if ('object' == typeof value) // Serialize objects as JSON
|
|
399
|
-
return JSON.stringify(value);
|
|
400
|
-
return value;
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Create a SQL fragment for building complex queries
|
|
404
|
-
*/ static fragment(sql, ...values) {
|
|
405
|
-
const parsed = this.parse(sql, values);
|
|
406
|
-
return {
|
|
407
|
-
text: parsed.sql,
|
|
408
|
-
values: parsed.params
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Join multiple SQL fragments
|
|
413
|
-
*/ static join(fragments, separator = ' ') {
|
|
414
|
-
const text = fragments.map((f)=>f.text).join(separator);
|
|
415
|
-
const values = fragments.flatMap((f)=>f.values);
|
|
416
|
-
return {
|
|
417
|
-
text,
|
|
418
|
-
values
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Helper for building WHERE clauses from objects
|
|
423
|
-
*/ static where(conditions) {
|
|
424
|
-
const entries = Object.entries(conditions).filter(([, value])=>void 0 !== value);
|
|
425
|
-
if (0 === entries.length) return {
|
|
426
|
-
text: '',
|
|
427
|
-
values: []
|
|
428
|
-
};
|
|
429
|
-
const clauses = entries.map(([key, value])=>{
|
|
430
|
-
if (null === value) return `${this.escapeIdentifier(key)} IS NULL`;
|
|
431
|
-
if (Array.isArray(value)) {
|
|
432
|
-
const placeholders = value.map(()=>'?').join(', ');
|
|
433
|
-
return `${this.escapeIdentifier(key)} IN (${placeholders})`;
|
|
434
|
-
}
|
|
435
|
-
return `${this.escapeIdentifier(key)} = ?`;
|
|
436
|
-
});
|
|
437
|
-
const values = entries.flatMap(([, value])=>{
|
|
438
|
-
if (null === value) return [];
|
|
439
|
-
if (Array.isArray(value)) return value.map((v)=>this.convertValue(v));
|
|
440
|
-
return [
|
|
441
|
-
this.convertValue(value)
|
|
720
|
+
static insertValues(data) {
|
|
721
|
+
const records = Array.isArray(data) ? data : [
|
|
722
|
+
data
|
|
442
723
|
];
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Helper for building UPDATE SET clauses from objects
|
|
470
|
-
*/ static updateSet(data) {
|
|
471
|
-
const entries = Object.entries(data).filter(([, value])=>void 0 !== value);
|
|
472
|
-
if (0 === entries.length) throw new Error('No data provided for update');
|
|
473
|
-
const setClauses = entries.map(([key])=>`${this.escapeIdentifier(key)} = ?`).join(', ');
|
|
474
|
-
const values = entries.map(([, value])=>this.convertValue(value));
|
|
475
|
-
return {
|
|
476
|
-
text: setClauses,
|
|
477
|
-
values
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
// Export convenience functions
|
|
482
|
-
const sql_parser_sql = sql_parser_SQLParser.parse.bind(sql_parser_SQLParser);
|
|
483
|
-
sql_parser_SQLParser.raw.bind(sql_parser_SQLParser);
|
|
484
|
-
sql_parser_SQLParser.identifier.bind(sql_parser_SQLParser);
|
|
485
|
-
const fragment = sql_parser_SQLParser.fragment.bind(sql_parser_SQLParser);
|
|
486
|
-
sql_parser_SQLParser.join.bind(sql_parser_SQLParser);
|
|
487
|
-
sql_parser_SQLParser.where.bind(sql_parser_SQLParser);
|
|
488
|
-
sql_parser_SQLParser.insertValues.bind(sql_parser_SQLParser);
|
|
489
|
-
sql_parser_SQLParser.updateSet.bind(sql_parser_SQLParser);
|
|
490
|
-
/**
|
|
491
|
-
* Transaction implementation for ODBLite
|
|
492
|
-
* Note: SQLite transactions are simulated at the client level since ODBLite
|
|
493
|
-
* operates on individual queries. This provides a familiar API but doesn't
|
|
494
|
-
* provide true ACID guarantees across multiple HTTP requests.
|
|
495
|
-
*/ class ODBLiteTransaction {
|
|
496
|
-
httpClient;
|
|
497
|
-
isCommitted = false;
|
|
498
|
-
isRolledBack = false;
|
|
499
|
-
queries = [];
|
|
500
|
-
constructor(httpClient){
|
|
501
|
-
this.httpClient = httpClient;
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Execute a query within the transaction
|
|
505
|
-
* For SQLite, we'll queue queries and execute them in batch on commit
|
|
506
|
-
*/ async sql(sql, ...values) {
|
|
507
|
-
this.checkTransactionState();
|
|
508
|
-
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
509
|
-
// For read queries, execute immediately
|
|
510
|
-
if (this.isReadQuery(parsed.sql)) return await this.httpClient.query(parsed.sql, parsed.params);
|
|
511
|
-
// For write queries, queue them for batch execution
|
|
512
|
-
this.queries.push(parsed);
|
|
513
|
-
// Return a placeholder result for queued queries
|
|
514
|
-
return {
|
|
515
|
-
rows: [],
|
|
516
|
-
rowsAffected: 0,
|
|
517
|
-
executionTime: 0
|
|
518
|
-
};
|
|
724
|
+
if (0 === records.length) throw new Error('No data provided for insert');
|
|
725
|
+
const keys = Object.keys(records[0]);
|
|
726
|
+
const columns = keys.map((key)=>this.escapeIdentifier(key)).join(', ');
|
|
727
|
+
const valueClauses = records.map((record)=>{
|
|
728
|
+
const placeholders = keys.map(()=>'?').join(', ');
|
|
729
|
+
return `(${placeholders})`;
|
|
730
|
+
}).join(', ');
|
|
731
|
+
const values = records.flatMap((record)=>keys.map((key)=>this.convertValue(record[key])));
|
|
732
|
+
return {
|
|
733
|
+
text: `(${columns}) VALUES ${valueClauses}`,
|
|
734
|
+
values
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
static updateSet(data) {
|
|
738
|
+
const entries = Object.entries(data).filter(([, value])=>void 0 !== value);
|
|
739
|
+
if (0 === entries.length) throw new Error('No data provided for update');
|
|
740
|
+
const setClauses = entries.map(([key])=>`${this.escapeIdentifier(key)} = ?`).join(', ');
|
|
741
|
+
const values = entries.map(([, value])=>this.convertValue(value));
|
|
742
|
+
return {
|
|
743
|
+
text: setClauses,
|
|
744
|
+
values
|
|
745
|
+
};
|
|
746
|
+
}
|
|
519
747
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
748
|
+
const sql_parser_sql = sql_parser_SQLParser.parse.bind(sql_parser_SQLParser);
|
|
749
|
+
sql_parser_SQLParser.raw.bind(sql_parser_SQLParser);
|
|
750
|
+
sql_parser_SQLParser.identifier.bind(sql_parser_SQLParser);
|
|
751
|
+
const fragment = sql_parser_SQLParser.fragment.bind(sql_parser_SQLParser);
|
|
752
|
+
sql_parser_SQLParser.join.bind(sql_parser_SQLParser);
|
|
753
|
+
sql_parser_SQLParser.where.bind(sql_parser_SQLParser);
|
|
754
|
+
sql_parser_SQLParser.insertValues.bind(sql_parser_SQLParser);
|
|
755
|
+
sql_parser_SQLParser.updateSet.bind(sql_parser_SQLParser);
|
|
756
|
+
class ODBLiteTransaction {
|
|
757
|
+
httpClient;
|
|
758
|
+
isCommitted = false;
|
|
759
|
+
isRolledBack = false;
|
|
760
|
+
queries = [];
|
|
761
|
+
constructor(httpClient){
|
|
762
|
+
this.httpClient = httpClient;
|
|
763
|
+
}
|
|
764
|
+
async sql(sql, ...values) {
|
|
765
|
+
this.checkTransactionState();
|
|
766
|
+
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
767
|
+
if (this.isReadQuery(parsed.sql)) return await this.httpClient.query(parsed.sql, parsed.params);
|
|
768
|
+
this.queries.push(parsed);
|
|
769
|
+
return {
|
|
770
|
+
rows: [],
|
|
771
|
+
rowsAffected: 0,
|
|
772
|
+
executionTime: 0
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
async commit() {
|
|
776
|
+
this.checkTransactionState();
|
|
534
777
|
try {
|
|
535
|
-
await this.httpClient.query('
|
|
536
|
-
|
|
537
|
-
|
|
778
|
+
await this.httpClient.query('BEGIN');
|
|
779
|
+
for (const query of this.queries)await this.httpClient.query(query.sql, query.params);
|
|
780
|
+
await this.httpClient.query('COMMIT');
|
|
781
|
+
this.isCommitted = true;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
try {
|
|
784
|
+
await this.httpClient.query('ROLLBACK');
|
|
785
|
+
} catch (rollbackError) {}
|
|
786
|
+
this.isRolledBack = true;
|
|
787
|
+
throw new types_QueryError(`Transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`, void 0, void 0, error instanceof Error ? error : void 0);
|
|
538
788
|
}
|
|
539
|
-
this.isRolledBack = true;
|
|
540
|
-
throw new types_QueryError(`Transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`, void 0, void 0, error instanceof Error ? error : void 0);
|
|
541
789
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (this.queries.length > 0) await this.httpClient.query('ROLLBACK');
|
|
550
|
-
} finally{
|
|
551
|
-
this.isRolledBack = true;
|
|
790
|
+
async rollback() {
|
|
791
|
+
this.checkTransactionState();
|
|
792
|
+
try {
|
|
793
|
+
if (this.queries.length > 0) await this.httpClient.query('ROLLBACK');
|
|
794
|
+
} finally{
|
|
795
|
+
this.isRolledBack = true;
|
|
796
|
+
}
|
|
552
797
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
*/ checkTransactionState() {
|
|
557
|
-
if (this.isCommitted) throw new types_QueryError('Transaction has already been committed');
|
|
558
|
-
if (this.isRolledBack) throw new types_QueryError('Transaction has been rolled back');
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* Determine if a query is a read operation
|
|
562
|
-
*/ isReadQuery(sql) {
|
|
563
|
-
const trimmed = sql.trim().toUpperCase();
|
|
564
|
-
return trimmed.startsWith('SELECT') || trimmed.startsWith('WITH') || trimmed.startsWith('EXPLAIN') || trimmed.startsWith('PRAGMA');
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Create a simple transaction function that executes immediately
|
|
569
|
-
* This is more suitable for HTTP-based databases where true transactions
|
|
570
|
-
* across multiple requests are not practical
|
|
571
|
-
*/ function createSimpleTransaction(httpClient) {
|
|
572
|
-
let isActive = true;
|
|
573
|
-
// Create the callable transaction function with context awareness
|
|
574
|
-
const txFunction = (sql, ...values)=>{
|
|
575
|
-
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
576
|
-
// Handle template string queries (returns Promise)
|
|
577
|
-
if (Array.isArray(sql) && ('raw' in sql || 'string' == typeof sql[0] && values.length >= 0)) {
|
|
578
|
-
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
579
|
-
return httpClient.query(parsed.sql, parsed.params);
|
|
798
|
+
checkTransactionState() {
|
|
799
|
+
if (this.isCommitted) throw new types_QueryError('Transaction has already been committed');
|
|
800
|
+
if (this.isRolledBack) throw new types_QueryError('Transaction has been rolled back');
|
|
580
801
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
text: parsed.sql,
|
|
585
|
-
values: parsed.params
|
|
586
|
-
};
|
|
587
|
-
};
|
|
588
|
-
// Attach utility methods to the transaction function
|
|
589
|
-
txFunction.raw = (text)=>sql_parser_SQLParser.raw(text);
|
|
590
|
-
txFunction.identifier = (name)=>sql_parser_SQLParser.identifier(name);
|
|
591
|
-
// Add execute method for compatibility
|
|
592
|
-
txFunction.execute = async (sql, args)=>{
|
|
593
|
-
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
594
|
-
if ('string' == typeof sql) return await httpClient.query(sql, args || []);
|
|
595
|
-
return await httpClient.query(sql.sql, sql.args || []);
|
|
596
|
-
};
|
|
597
|
-
// Add query method for compatibility
|
|
598
|
-
txFunction.query = async (sql, params = [])=>{
|
|
599
|
-
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
600
|
-
return await httpClient.query(sql, params);
|
|
601
|
-
};
|
|
602
|
-
txFunction.commit = async ()=>{
|
|
603
|
-
isActive = false;
|
|
604
|
-
// No-op for simple transactions
|
|
605
|
-
};
|
|
606
|
-
txFunction.rollback = async ()=>{
|
|
607
|
-
isActive = false;
|
|
608
|
-
// No-op for simple transactions - individual queries are atomic
|
|
609
|
-
};
|
|
610
|
-
txFunction.savepoint = async (callback)=>{
|
|
611
|
-
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
612
|
-
// Create a nested transaction for the savepoint
|
|
613
|
-
const savepointTx = createSimpleTransaction(httpClient);
|
|
614
|
-
try {
|
|
615
|
-
// Execute the callback with the savepoint transaction
|
|
616
|
-
const result = await callback(savepointTx);
|
|
617
|
-
// Commit the savepoint (no-op for simple transactions)
|
|
618
|
-
await savepointTx.commit();
|
|
619
|
-
return result;
|
|
620
|
-
} catch (error) {
|
|
621
|
-
// Rollback the savepoint on error
|
|
622
|
-
await savepointTx.rollback();
|
|
623
|
-
throw error;
|
|
802
|
+
isReadQuery(sql) {
|
|
803
|
+
const trimmed = sql.trim().toUpperCase();
|
|
804
|
+
return trimmed.startsWith('SELECT') || trimmed.startsWith('WITH') || trimmed.startsWith('EXPLAIN') || trimmed.startsWith('PRAGMA');
|
|
624
805
|
}
|
|
625
|
-
};
|
|
626
|
-
return txFunction;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Simple transaction implementation that executes immediately
|
|
630
|
-
* This is more suitable for HTTP-based databases where true transactions
|
|
631
|
-
* across multiple requests are not practical
|
|
632
|
-
*/ class SimpleTransaction {
|
|
633
|
-
httpClient;
|
|
634
|
-
isActive = true;
|
|
635
|
-
constructor(httpClient){
|
|
636
|
-
this.httpClient = httpClient;
|
|
637
|
-
}
|
|
638
|
-
async sql(sql, ...values) {
|
|
639
|
-
if (!this.isActive) throw new types_QueryError('Transaction is no longer active');
|
|
640
|
-
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
641
|
-
return await this.httpClient.query(parsed.sql, parsed.params);
|
|
642
806
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
async rollback() {
|
|
648
|
-
this.isActive = false;
|
|
649
|
-
// No-op for simple transactions - individual queries are atomic
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Main ODBLite client that provides postgres.js-like interface
|
|
654
|
-
*/ class ODBLiteClient {
|
|
655
|
-
httpClient;
|
|
656
|
-
config;
|
|
657
|
-
sql;
|
|
658
|
-
constructor(config){
|
|
659
|
-
this.config = config;
|
|
660
|
-
this.httpClient = new HTTPClient(config);
|
|
661
|
-
// Create the callable sql function with attached utility methods
|
|
662
|
-
const sqlFunction = (sql, ...values)=>{
|
|
663
|
-
// Handle template string queries (returns Promise)
|
|
807
|
+
function createSimpleTransaction(httpClient) {
|
|
808
|
+
let isActive = true;
|
|
809
|
+
const txFunction = (sql, ...values)=>{
|
|
810
|
+
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
664
811
|
if (Array.isArray(sql) && ('raw' in sql || 'string' == typeof sql[0] && values.length >= 0)) {
|
|
665
812
|
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
666
|
-
return
|
|
813
|
+
return httpClient.query(parsed.sql, parsed.params);
|
|
667
814
|
}
|
|
668
|
-
// Handle direct object/array inputs (returns SQLFragment for composing)
|
|
669
815
|
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
670
816
|
return {
|
|
671
817
|
text: parsed.sql,
|
|
672
818
|
values: parsed.params
|
|
673
819
|
};
|
|
674
820
|
};
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
sqlFunction.execute = async (sql, args)=>{
|
|
682
|
-
if ('string' == typeof sql) return await this.httpClient.query(sql, args || []);
|
|
683
|
-
return await this.httpClient.query(sql.sql, sql.args || []);
|
|
821
|
+
txFunction.raw = (text)=>sql_parser_SQLParser.raw(text);
|
|
822
|
+
txFunction.identifier = (name)=>sql_parser_SQLParser.identifier(name);
|
|
823
|
+
txFunction.execute = async (sql, args)=>{
|
|
824
|
+
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
825
|
+
if ('string' == typeof sql) return await httpClient.query(sql, args || []);
|
|
826
|
+
return await httpClient.query(sql.sql, sql.args || []);
|
|
684
827
|
};
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
return this.executeTransactionWithCallback(callback, modeOrCallback);
|
|
692
|
-
// begin() - traditional style
|
|
693
|
-
return createSimpleTransaction(this.httpClient);
|
|
828
|
+
txFunction.query = async (sql, params = [])=>{
|
|
829
|
+
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
830
|
+
return await httpClient.query(sql, params);
|
|
831
|
+
};
|
|
832
|
+
txFunction.commit = async ()=>{
|
|
833
|
+
isActive = false;
|
|
694
834
|
};
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
// No-op for HTTP-based client
|
|
835
|
+
txFunction.rollback = async ()=>{
|
|
836
|
+
isActive = false;
|
|
698
837
|
};
|
|
699
|
-
|
|
838
|
+
txFunction.savepoint = async (callback)=>{
|
|
839
|
+
if (!isActive) throw new types_QueryError('Transaction is no longer active');
|
|
840
|
+
const savepointTx = createSimpleTransaction(httpClient);
|
|
841
|
+
try {
|
|
842
|
+
const result = await callback(savepointTx);
|
|
843
|
+
await savepointTx.commit();
|
|
844
|
+
return result;
|
|
845
|
+
} catch (error) {
|
|
846
|
+
await savepointTx.rollback();
|
|
847
|
+
throw error;
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
return txFunction;
|
|
851
|
+
}
|
|
852
|
+
class SimpleTransaction {
|
|
853
|
+
httpClient;
|
|
854
|
+
isActive = true;
|
|
855
|
+
constructor(httpClient){
|
|
856
|
+
this.httpClient = httpClient;
|
|
857
|
+
}
|
|
858
|
+
async sql(sql, ...values) {
|
|
859
|
+
if (!this.isActive) throw new types_QueryError('Transaction is no longer active');
|
|
860
|
+
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
861
|
+
return await this.httpClient.query(parsed.sql, parsed.params);
|
|
862
|
+
}
|
|
863
|
+
async commit() {
|
|
864
|
+
this.isActive = false;
|
|
865
|
+
}
|
|
866
|
+
async rollback() {
|
|
867
|
+
this.isActive = false;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
class ODBLiteClient {
|
|
871
|
+
httpClient;
|
|
872
|
+
config;
|
|
873
|
+
sql;
|
|
874
|
+
constructor(config){
|
|
875
|
+
this.config = config;
|
|
876
|
+
this.httpClient = new HTTPClient(config);
|
|
877
|
+
const sqlFunction = (sql, ...values)=>{
|
|
878
|
+
if (Array.isArray(sql) && ('raw' in sql || 'string' == typeof sql[0] && values.length >= 0)) {
|
|
879
|
+
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
880
|
+
return this.httpClient.query(parsed.sql, parsed.params);
|
|
881
|
+
}
|
|
882
|
+
const parsed = sql_parser_SQLParser.parse(sql, values);
|
|
883
|
+
return {
|
|
884
|
+
text: parsed.sql,
|
|
885
|
+
values: parsed.params
|
|
886
|
+
};
|
|
887
|
+
};
|
|
888
|
+
sqlFunction.raw = (text)=>sql_parser_SQLParser.raw(text);
|
|
889
|
+
sqlFunction.identifier = (name)=>sql_parser_SQLParser.identifier(name);
|
|
890
|
+
sqlFunction.query = async (sql, params = [])=>await this.httpClient.query(sql, params);
|
|
891
|
+
sqlFunction.execute = async (sql, args)=>{
|
|
892
|
+
if ('string' == typeof sql) return await this.httpClient.query(sql, args || []);
|
|
893
|
+
return await this.httpClient.query(sql.sql, sql.args || []);
|
|
894
|
+
};
|
|
895
|
+
sqlFunction.begin = async (modeOrCallback, callback)=>{
|
|
896
|
+
if ('function' == typeof modeOrCallback) return this.executeTransactionWithCallback(modeOrCallback);
|
|
897
|
+
if ('string' == typeof modeOrCallback && callback) return this.executeTransactionWithCallback(callback, modeOrCallback);
|
|
898
|
+
return createSimpleTransaction(this.httpClient);
|
|
899
|
+
};
|
|
900
|
+
sqlFunction.ping = async ()=>await this.httpClient.ping();
|
|
901
|
+
sqlFunction.end = async ()=>{};
|
|
902
|
+
sqlFunction.setDatabase = (databaseId)=>{
|
|
903
|
+
this.httpClient.setDatabase(databaseId);
|
|
904
|
+
this.config.databaseId = databaseId;
|
|
905
|
+
return sqlFunction;
|
|
906
|
+
};
|
|
907
|
+
sqlFunction.getDatabaseInfo = async ()=>await this.httpClient.getDatabaseInfo();
|
|
908
|
+
sqlFunction.configure = (updates)=>{
|
|
909
|
+
const newConfig = {
|
|
910
|
+
...this.config,
|
|
911
|
+
...updates
|
|
912
|
+
};
|
|
913
|
+
return new ODBLiteClient(newConfig).sql;
|
|
914
|
+
};
|
|
915
|
+
this.sql = sqlFunction;
|
|
916
|
+
}
|
|
917
|
+
async executeTransactionWithCallback(callback, mode) {
|
|
918
|
+
const tx = createSimpleTransaction(this.httpClient);
|
|
919
|
+
try {
|
|
920
|
+
const result = await callback(tx);
|
|
921
|
+
await tx.commit();
|
|
922
|
+
return result;
|
|
923
|
+
} catch (error) {
|
|
924
|
+
await tx.rollback();
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
async query(sql, params = []) {
|
|
929
|
+
return await this.httpClient.query(sql, params);
|
|
930
|
+
}
|
|
931
|
+
async begin() {
|
|
932
|
+
return createSimpleTransaction(this.httpClient);
|
|
933
|
+
}
|
|
934
|
+
async ping() {
|
|
935
|
+
return await this.httpClient.ping();
|
|
936
|
+
}
|
|
937
|
+
async end() {}
|
|
938
|
+
setDatabase(databaseId) {
|
|
700
939
|
this.httpClient.setDatabase(databaseId);
|
|
701
940
|
this.config.databaseId = databaseId;
|
|
702
|
-
return
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
|
|
941
|
+
return this;
|
|
942
|
+
}
|
|
943
|
+
async getDatabaseInfo() {
|
|
944
|
+
return await this.httpClient.getDatabaseInfo();
|
|
945
|
+
}
|
|
946
|
+
configure(updates) {
|
|
706
947
|
const newConfig = {
|
|
707
948
|
...this.config,
|
|
708
949
|
...updates
|
|
709
950
|
};
|
|
710
|
-
return new ODBLiteClient(newConfig)
|
|
711
|
-
};
|
|
712
|
-
this.sql = sqlFunction;
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* Execute a transaction with callback (postgres.js style)
|
|
716
|
-
*/ async executeTransactionWithCallback(callback, mode) {
|
|
717
|
-
const tx = createSimpleTransaction(this.httpClient);
|
|
718
|
-
try {
|
|
719
|
-
// Execute the callback with the transaction
|
|
720
|
-
const result = await callback(tx);
|
|
721
|
-
// Commit the transaction
|
|
722
|
-
await tx.commit();
|
|
723
|
-
return result;
|
|
724
|
-
} catch (error) {
|
|
725
|
-
// Rollback on any error
|
|
726
|
-
await tx.rollback();
|
|
727
|
-
throw error;
|
|
951
|
+
return new ODBLiteClient(newConfig);
|
|
728
952
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Close connection (no-op for HTTP client)
|
|
749
|
-
*/ async end() {
|
|
750
|
-
// No-op for HTTP-based client
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Set the database ID for queries
|
|
754
|
-
*/ setDatabase(databaseId) {
|
|
755
|
-
this.httpClient.setDatabase(databaseId);
|
|
756
|
-
this.config.databaseId = databaseId;
|
|
757
|
-
return this;
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Get database information
|
|
761
|
-
*/ async getDatabaseInfo() {
|
|
762
|
-
return await this.httpClient.getDatabaseInfo();
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Create a new client instance with updated configuration
|
|
766
|
-
*/ configure(updates) {
|
|
767
|
-
const newConfig = {
|
|
768
|
-
...this.config,
|
|
769
|
-
...updates
|
|
770
|
-
};
|
|
771
|
-
return new ODBLiteClient(newConfig);
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* Create raw SQL that won't be parameterized
|
|
775
|
-
* Usage: sql`SELECT * FROM ${raw('users')}`
|
|
776
|
-
*/ static raw(text) {
|
|
777
|
-
return sql_parser_SQLParser.raw(text);
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Escape identifier (table/column names)
|
|
781
|
-
* Usage: sql`SELECT * FROM ${identifier('user-table')}`
|
|
782
|
-
*/ static identifier(name) {
|
|
783
|
-
return sql_parser_SQLParser.identifier(name);
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Build WHERE clause from object
|
|
787
|
-
* Usage: const whereClause = ODBLiteClient.where({ id: 1, name: 'John' });
|
|
788
|
-
*/ static where(conditions) {
|
|
789
|
-
return sql_parser_SQLParser.where(conditions);
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Build INSERT VALUES from object(s)
|
|
793
|
-
* Usage: const insertClause = ODBLiteClient.insertValues({ name: 'John', age: 30 });
|
|
794
|
-
*/ static insertValues(data) {
|
|
795
|
-
return sql_parser_SQLParser.insertValues(data);
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Build UPDATE SET clause from object
|
|
799
|
-
* Usage: const setClause = ODBLiteClient.updateSet({ name: 'John', age: 30 });
|
|
800
|
-
*/ static updateSet(data) {
|
|
801
|
-
return sql_parser_SQLParser.updateSet(data);
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Join SQL fragments
|
|
805
|
-
* Usage: const query = ODBLiteClient.join([baseQuery, whereClause], ' WHERE ');
|
|
806
|
-
*/ static join(fragments, separator = ' ') {
|
|
807
|
-
return sql_parser_SQLParser.join(fragments, separator);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
function odblite(configOrBaseUrl, apiKey, databaseId) {
|
|
811
|
-
const client = 'string' == typeof configOrBaseUrl ? new ODBLiteClient({
|
|
812
|
-
baseUrl: configOrBaseUrl,
|
|
813
|
-
apiKey: apiKey,
|
|
814
|
-
databaseId
|
|
815
|
-
}) : new ODBLiteClient(configOrBaseUrl);
|
|
816
|
-
return client.sql;
|
|
817
|
-
}
|
|
818
|
-
// Export static utility functions
|
|
819
|
-
ODBLiteClient.raw;
|
|
820
|
-
ODBLiteClient.identifier;
|
|
821
|
-
ODBLiteClient.where;
|
|
822
|
-
ODBLiteClient.insertValues;
|
|
823
|
-
ODBLiteClient.updateSet;
|
|
824
|
-
ODBLiteClient.join;
|
|
825
|
-
/**
|
|
826
|
-
* Service Client - High-level client for managing tenant databases via ODB-Lite Tenant API
|
|
827
|
-
*
|
|
828
|
-
* This client provides automatic database provisioning and management for multi-tenant applications.
|
|
829
|
-
* It handles:
|
|
830
|
-
* - Automatic database creation on first use
|
|
831
|
-
* - Database hash caching for performance
|
|
832
|
-
* - Tenant API integration with ODB-Lite
|
|
833
|
-
* - Query execution via ODB-Lite's query API
|
|
834
|
-
*
|
|
835
|
-
* @example
|
|
836
|
-
* ```typescript
|
|
837
|
-
* const service = new ServiceClient({
|
|
838
|
-
* baseUrl: 'http://localhost:8671',
|
|
839
|
-
* apiKey: 'odblite_tenant_key'
|
|
840
|
-
* });
|
|
841
|
-
*
|
|
842
|
-
* // Automatically creates database if it doesn't exist
|
|
843
|
-
* const dbHash = await service.ensureDatabaseForTenant('wallet', 'tenant-123');
|
|
844
|
-
*
|
|
845
|
-
* // Execute queries
|
|
846
|
-
* const result = await service.query(dbHash, 'SELECT * FROM wallets', []);
|
|
847
|
-
* ```
|
|
848
|
-
*/ /**
|
|
849
|
-
* Service Client for managing tenant databases in ODB-Lite
|
|
850
|
-
*
|
|
851
|
-
* This is a higher-level client that sits on top of the base ODB client.
|
|
852
|
-
* It provides automatic database provisioning and management for services
|
|
853
|
-
* that need per-tenant database isolation.
|
|
854
|
-
*/ class ServiceClient {
|
|
855
|
-
apiUrl;
|
|
856
|
-
apiKey;
|
|
857
|
-
databaseCache;
|
|
858
|
-
constructor(config){
|
|
859
|
-
this.apiUrl = config.baseUrl;
|
|
860
|
-
this.apiKey = config.apiKey;
|
|
861
|
-
this.databaseCache = new Map();
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Get or create a database for a tenant
|
|
865
|
-
*
|
|
866
|
-
* This is the main method used by services. It will:
|
|
867
|
-
* 1. Check the cache for an existing database hash
|
|
868
|
-
* 2. Query ODB-Lite to see if the database exists
|
|
869
|
-
* 3. Create the database if it doesn't exist
|
|
870
|
-
* 4. Cache and return the database hash
|
|
871
|
-
*
|
|
872
|
-
* @param prefix - Database name prefix (e.g., 'wallet', 'tracking')
|
|
873
|
-
* @param tenantId - Tenant identifier
|
|
874
|
-
* @returns Database hash for querying
|
|
875
|
-
*
|
|
876
|
-
* @example
|
|
877
|
-
* ```typescript
|
|
878
|
-
* const hash = await service.ensureDatabaseForTenant('wallet', 'tenant-123');
|
|
879
|
-
* // Returns hash for database named 'wallet_tenant-123'
|
|
880
|
-
* ```
|
|
881
|
-
*/ async ensureDatabaseForTenant(prefix, tenantId) {
|
|
882
|
-
const cacheKey = `${prefix}_${tenantId}`;
|
|
883
|
-
console.log(`📊 Ensuring database for ${cacheKey}`);
|
|
884
|
-
// Check cache first
|
|
885
|
-
const cached = this.databaseCache.get(cacheKey);
|
|
886
|
-
if (cached) {
|
|
887
|
-
console.log(`✅ Found cached database hash: ${cached}`);
|
|
888
|
-
return cached;
|
|
889
|
-
}
|
|
890
|
-
try {
|
|
891
|
-
// Check if database already exists
|
|
892
|
-
console.log(`🔍 Checking if database exists: ${cacheKey}`);
|
|
893
|
-
const existing = await this.getDatabaseByName(cacheKey);
|
|
894
|
-
if (existing) {
|
|
895
|
-
console.log(`✅ Database already exists: ${cacheKey} (${existing.hash})`);
|
|
896
|
-
this.databaseCache.set(cacheKey, existing.hash);
|
|
897
|
-
return existing.hash;
|
|
898
|
-
}
|
|
899
|
-
// Create new database
|
|
900
|
-
console.log(`🆕 Creating new database: ${cacheKey}`);
|
|
901
|
-
const nodes = await this.listNodes();
|
|
902
|
-
console.log(`📡 Available nodes: ${nodes.length}`);
|
|
903
|
-
if (0 === nodes.length) throw new Error('No available nodes to create database');
|
|
904
|
-
// Use first healthy node
|
|
905
|
-
const node = nodes.find((n)=>'healthy' === n.status) || nodes[0];
|
|
906
|
-
if (!node) throw new Error('No available nodes to create database');
|
|
907
|
-
console.log(`🎯 Using node: ${node.nodeId}`);
|
|
908
|
-
const database = await this.createDatabase(cacheKey, node.nodeId);
|
|
909
|
-
console.log(`✅ Database created successfully: ${database.hash}`);
|
|
910
|
-
this.databaseCache.set(cacheKey, database.hash);
|
|
911
|
-
return database.hash;
|
|
912
|
-
} catch (error) {
|
|
913
|
-
console.error(`❌ Error ensuring database for ${cacheKey}:`, error.message);
|
|
914
|
-
throw error;
|
|
953
|
+
static raw(text) {
|
|
954
|
+
return sql_parser_SQLParser.raw(text);
|
|
955
|
+
}
|
|
956
|
+
static identifier(name) {
|
|
957
|
+
return sql_parser_SQLParser.identifier(name);
|
|
958
|
+
}
|
|
959
|
+
static where(conditions) {
|
|
960
|
+
return sql_parser_SQLParser.where(conditions);
|
|
961
|
+
}
|
|
962
|
+
static insertValues(data) {
|
|
963
|
+
return sql_parser_SQLParser.insertValues(data);
|
|
964
|
+
}
|
|
965
|
+
static updateSet(data) {
|
|
966
|
+
return sql_parser_SQLParser.updateSet(data);
|
|
967
|
+
}
|
|
968
|
+
static join(fragments, separator = ' ') {
|
|
969
|
+
return sql_parser_SQLParser.join(fragments, separator);
|
|
915
970
|
}
|
|
916
971
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
972
|
+
function odblite(configOrBaseUrl, apiKey, databaseId) {
|
|
973
|
+
const client = 'string' == typeof configOrBaseUrl ? new ODBLiteClient({
|
|
974
|
+
baseUrl: configOrBaseUrl,
|
|
975
|
+
apiKey: apiKey,
|
|
976
|
+
databaseId
|
|
977
|
+
}) : new ODBLiteClient(configOrBaseUrl);
|
|
978
|
+
return client.sql;
|
|
979
|
+
}
|
|
980
|
+
ODBLiteClient.raw;
|
|
981
|
+
ODBLiteClient.identifier;
|
|
982
|
+
ODBLiteClient.where;
|
|
983
|
+
ODBLiteClient.insertValues;
|
|
984
|
+
ODBLiteClient.updateSet;
|
|
985
|
+
ODBLiteClient.join;
|
|
986
|
+
class ServiceClient {
|
|
987
|
+
apiUrl;
|
|
988
|
+
apiKey;
|
|
989
|
+
databaseCache;
|
|
990
|
+
constructor(config){
|
|
991
|
+
this.apiUrl = config.baseUrl;
|
|
992
|
+
this.apiKey = config.apiKey;
|
|
993
|
+
this.databaseCache = new Map();
|
|
994
|
+
}
|
|
995
|
+
async ensureDatabaseForTenant(prefix, tenantId) {
|
|
996
|
+
const cacheKey = `${prefix}_${tenantId}`;
|
|
997
|
+
console.log(`📊 Ensuring database for ${cacheKey}`);
|
|
998
|
+
const cached = this.databaseCache.get(cacheKey);
|
|
999
|
+
if (cached) {
|
|
1000
|
+
console.log(`✅ Found cached database hash: ${cached}`);
|
|
1001
|
+
return cached;
|
|
927
1002
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
body: JSON.stringify(body)
|
|
951
|
-
});
|
|
952
|
-
const result = await response.json();
|
|
953
|
-
if (!result.success) throw new Error(result.error || 'Failed to create database');
|
|
954
|
-
return result.database;
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* Get database details by hash
|
|
958
|
-
*
|
|
959
|
-
* @param hash - Database hash
|
|
960
|
-
* @returns Database object
|
|
961
|
-
*/ async getDatabase(hash) {
|
|
962
|
-
const response = await fetch(`${this.apiUrl}/api/tenant/databases/${hash}`, {
|
|
963
|
-
headers: {
|
|
964
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
1003
|
+
try {
|
|
1004
|
+
console.log(`🔍 Checking if database exists: ${cacheKey}`);
|
|
1005
|
+
const existing = await this.getDatabaseByName(cacheKey);
|
|
1006
|
+
if (existing) {
|
|
1007
|
+
console.log(`✅ Database already exists: ${cacheKey} (${existing.hash})`);
|
|
1008
|
+
this.databaseCache.set(cacheKey, existing.hash);
|
|
1009
|
+
return existing.hash;
|
|
1010
|
+
}
|
|
1011
|
+
console.log(`🆕 Creating new database: ${cacheKey}`);
|
|
1012
|
+
const nodes = await this.listNodes();
|
|
1013
|
+
console.log(`📡 Available nodes: ${nodes.length}`);
|
|
1014
|
+
if (0 === nodes.length) throw new Error('No available nodes to create database');
|
|
1015
|
+
const node = nodes.find((n)=>'healthy' === n.status) || nodes[0];
|
|
1016
|
+
if (!node) throw new Error('No available nodes to create database');
|
|
1017
|
+
console.log(`🎯 Using node: ${node.nodeId}`);
|
|
1018
|
+
const database = await this.createDatabase(cacheKey, node.nodeId);
|
|
1019
|
+
console.log(`✅ Database created successfully: ${database.hash}`);
|
|
1020
|
+
this.databaseCache.set(cacheKey, database.hash);
|
|
1021
|
+
return database.hash;
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
console.error(`❌ Error ensuring database for ${cacheKey}:`, error.message);
|
|
1024
|
+
throw error;
|
|
965
1025
|
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1026
|
+
}
|
|
1027
|
+
async listDatabases() {
|
|
1028
|
+
const response = await fetch(`${this.apiUrl}/api/tenant/databases`, {
|
|
1029
|
+
headers: {
|
|
1030
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
const result = await response.json();
|
|
1034
|
+
if (!result.success) throw new Error(result.error || 'Failed to list databases');
|
|
1035
|
+
return result.databases;
|
|
1036
|
+
}
|
|
1037
|
+
async createDatabase(name, nodeId) {
|
|
1038
|
+
const body = {
|
|
1039
|
+
name
|
|
1040
|
+
};
|
|
1041
|
+
if (nodeId) body.nodeId = nodeId;
|
|
1042
|
+
const response = await fetch(`${this.apiUrl}/api/tenant/databases`, {
|
|
1043
|
+
method: 'POST',
|
|
1044
|
+
headers: {
|
|
1045
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1046
|
+
'Content-Type': 'application/json'
|
|
1047
|
+
},
|
|
1048
|
+
body: JSON.stringify(body)
|
|
1049
|
+
});
|
|
1050
|
+
const result = await response.json();
|
|
1051
|
+
if (!result.success) throw new Error(result.error || 'Failed to create database');
|
|
1052
|
+
return result.database;
|
|
1053
|
+
}
|
|
1054
|
+
async getDatabase(hash) {
|
|
1055
|
+
const response = await fetch(`${this.apiUrl}/api/tenant/databases/${hash}`, {
|
|
1056
|
+
headers: {
|
|
1057
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
const result = await response.json();
|
|
1061
|
+
if (!result.success) throw new Error(result.error || 'Failed to get database');
|
|
1062
|
+
return result.database;
|
|
1063
|
+
}
|
|
1064
|
+
async getDatabaseByName(name) {
|
|
1065
|
+
const response = await fetch(`${this.apiUrl}/api/tenant/databases/by-name/${name}`, {
|
|
1066
|
+
headers: {
|
|
1067
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
if (404 === response.status) return null;
|
|
1071
|
+
const result = await response.json();
|
|
1072
|
+
if (!result.success) throw new Error(result.error || 'Failed to get database');
|
|
1073
|
+
return result.database;
|
|
1074
|
+
}
|
|
1075
|
+
async deleteDatabase(hash) {
|
|
1076
|
+
const response = await fetch(`${this.apiUrl}/api/tenant/databases/${hash}`, {
|
|
1077
|
+
method: 'DELETE',
|
|
1078
|
+
headers: {
|
|
1079
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
const result = await response.json();
|
|
1083
|
+
if (!result.success) throw new Error(result.error || 'Failed to delete database');
|
|
1084
|
+
for (const [key, cachedHash] of this.databaseCache.entries())if (cachedHash === hash) {
|
|
1085
|
+
this.databaseCache.delete(key);
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
async listNodes() {
|
|
1090
|
+
const response = await fetch(`${this.apiUrl}/api/tenant/nodes`, {
|
|
1091
|
+
headers: {
|
|
1092
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
const result = await response.json();
|
|
1096
|
+
if (!result.success) throw new Error(result.error || 'Failed to list nodes');
|
|
1097
|
+
return result.nodes;
|
|
1098
|
+
}
|
|
1099
|
+
async query(databaseHash, sql, params = []) {
|
|
1100
|
+
const response = await fetch(`${this.apiUrl}/query/${databaseHash}`, {
|
|
1101
|
+
method: 'POST',
|
|
1102
|
+
headers: {
|
|
1103
|
+
'Content-Type': 'application/json',
|
|
1104
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1105
|
+
},
|
|
1106
|
+
body: JSON.stringify({
|
|
1107
|
+
sql,
|
|
1108
|
+
params
|
|
1109
|
+
})
|
|
1110
|
+
});
|
|
1111
|
+
const result = await response.json();
|
|
1112
|
+
if (!result.success) throw new Error(result.error || 'Query failed');
|
|
1113
|
+
return result.data;
|
|
1114
|
+
}
|
|
1115
|
+
clearCache() {
|
|
1116
|
+
this.databaseCache.clear();
|
|
1117
|
+
}
|
|
1118
|
+
getCachedHash(prefix, tenantId) {
|
|
1119
|
+
const cacheKey = `${prefix}_${tenantId}`;
|
|
1120
|
+
return this.databaseCache.get(cacheKey);
|
|
1121
|
+
}
|
|
1122
|
+
setCachedHash(prefix, tenantId, hash) {
|
|
1123
|
+
const cacheKey = `${prefix}_${tenantId}`;
|
|
1124
|
+
this.databaseCache.set(cacheKey, hash);
|
|
1125
|
+
}
|
|
970
1126
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1127
|
+
const external_bun_sqlite_namespaceObject = require("bun:sqlite");
|
|
1128
|
+
function convertTemplateToQuery(strings, values) {
|
|
1129
|
+
let sql = '';
|
|
1130
|
+
const args = [];
|
|
1131
|
+
for(let i = 0; i < strings.length; i++){
|
|
1132
|
+
sql += strings[i];
|
|
1133
|
+
if (i < values.length) {
|
|
1134
|
+
const value = values[i];
|
|
1135
|
+
if (isSqlFragment(value)) {
|
|
1136
|
+
sql += value.sql;
|
|
1137
|
+
args.push(...value.args);
|
|
1138
|
+
} else if (Array.isArray(value)) if (value.length > 0 && Array.isArray(value[0])) {
|
|
1139
|
+
const rowPlaceholders = value.map((row)=>`(${row.map(()=>'?').join(', ')})`).join(', ');
|
|
1140
|
+
sql += rowPlaceholders;
|
|
1141
|
+
args.push(...value.flat());
|
|
1142
|
+
} else {
|
|
1143
|
+
const placeholders = value.map(()=>'?').join(', ');
|
|
1144
|
+
sql += `(${placeholders})`;
|
|
1145
|
+
args.push(...value);
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
sql += '?';
|
|
1149
|
+
args.push(value);
|
|
1150
|
+
}
|
|
980
1151
|
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
sql,
|
|
1155
|
+
args
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function isSqlFragment(value) {
|
|
1159
|
+
return value && 'object' == typeof value && true === value._isSqlFragment;
|
|
986
1160
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1161
|
+
function sql_template_sql(value, ...keys) {
|
|
1162
|
+
let columnKeys = [];
|
|
1163
|
+
if (keys.length > 0) columnKeys = Array.isArray(keys[0]) ? keys[0] : keys;
|
|
1164
|
+
if ('string' == typeof value && 0 === columnKeys.length) return {
|
|
1165
|
+
_isSqlFragment: true,
|
|
1166
|
+
sql: value,
|
|
1167
|
+
args: []
|
|
1168
|
+
};
|
|
1169
|
+
if (Array.isArray(value) && value.length > 0 && 'string' == typeof value[0]) return {
|
|
1170
|
+
_isSqlFragment: true,
|
|
1171
|
+
sql: value.join(', '),
|
|
1172
|
+
args: []
|
|
1173
|
+
};
|
|
1174
|
+
if (Array.isArray(value) && value.length > 0 && 'object' == typeof value[0] && !Array.isArray(value[0])) {
|
|
1175
|
+
if (0 === columnKeys.length) columnKeys = Object.keys(value[0]);
|
|
1176
|
+
const allValues = [];
|
|
1177
|
+
const rowPlaceholders = [];
|
|
1178
|
+
for (const obj of value){
|
|
1179
|
+
const rowValues = columnKeys.map((key)=>obj[key]);
|
|
1180
|
+
allValues.push(...rowValues);
|
|
1181
|
+
rowPlaceholders.push(`(${columnKeys.map(()=>'?').join(', ')})`);
|
|
996
1182
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1183
|
+
return {
|
|
1184
|
+
_isSqlFragment: true,
|
|
1185
|
+
sql: `(${columnKeys.join(', ')}) VALUES ${rowPlaceholders.join(', ')}`,
|
|
1186
|
+
args: allValues
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
if (value && 'object' == typeof value && !Array.isArray(value)) {
|
|
1190
|
+
if (0 === columnKeys.length) columnKeys = Object.keys(value);
|
|
1191
|
+
const values = columnKeys.map((key)=>value[key]);
|
|
1192
|
+
const placeholders = columnKeys.map(()=>'?').join(', ');
|
|
1193
|
+
return {
|
|
1194
|
+
_isSqlFragment: true,
|
|
1195
|
+
sql: `(${columnKeys.join(', ')}) VALUES (${placeholders})`,
|
|
1196
|
+
args: values
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
|
|
1200
|
+
const rowPlaceholders = value.map((row)=>`(${row.map(()=>'?').join(', ')})`).join(', ');
|
|
1201
|
+
return {
|
|
1202
|
+
_isSqlFragment: true,
|
|
1203
|
+
sql: rowPlaceholders,
|
|
1204
|
+
args: value.flat()
|
|
1205
|
+
};
|
|
1004
1206
|
}
|
|
1207
|
+
return {
|
|
1208
|
+
_isSqlFragment: true,
|
|
1209
|
+
sql: '',
|
|
1210
|
+
args: []
|
|
1211
|
+
};
|
|
1005
1212
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
headers: {
|
|
1013
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
1014
|
-
}
|
|
1015
|
-
});
|
|
1016
|
-
const result = await response.json();
|
|
1017
|
-
if (!result.success) throw new Error(result.error || 'Failed to list nodes');
|
|
1018
|
-
return result.nodes;
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Execute a query on a specific database
|
|
1022
|
-
*
|
|
1023
|
-
* This is a low-level query method. For most use cases, you should use
|
|
1024
|
-
* the full ODB client (`odblite()` function) instead, which provides
|
|
1025
|
-
* template tag support and better ergonomics.
|
|
1026
|
-
*
|
|
1027
|
-
* @param databaseHash - Hash of the database to query
|
|
1028
|
-
* @param sql - SQL query string
|
|
1029
|
-
* @param params - Query parameters
|
|
1030
|
-
* @returns Query result
|
|
1031
|
-
*/ async query(databaseHash, sql, params = []) {
|
|
1032
|
-
const response = await fetch(`${this.apiUrl}/query/${databaseHash}`, {
|
|
1033
|
-
method: 'POST',
|
|
1034
|
-
headers: {
|
|
1035
|
-
'Content-Type': 'application/json',
|
|
1036
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
1037
|
-
},
|
|
1038
|
-
body: JSON.stringify({
|
|
1039
|
-
sql,
|
|
1040
|
-
params
|
|
1041
|
-
})
|
|
1042
|
-
});
|
|
1043
|
-
const result = await response.json();
|
|
1044
|
-
if (!result.success) throw new Error(result.error || 'Query failed');
|
|
1045
|
-
return result.data;
|
|
1046
|
-
}
|
|
1047
|
-
/**
|
|
1048
|
-
* Clear the database hash cache
|
|
1049
|
-
*
|
|
1050
|
-
* Useful when database mappings have changed or for testing.
|
|
1051
|
-
*/ clearCache() {
|
|
1052
|
-
this.databaseCache.clear();
|
|
1213
|
+
function empty() {
|
|
1214
|
+
return {
|
|
1215
|
+
_isSqlFragment: true,
|
|
1216
|
+
sql: '',
|
|
1217
|
+
args: []
|
|
1218
|
+
};
|
|
1053
1219
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
*/ getCachedHash(prefix, tenantId) {
|
|
1061
|
-
const cacheKey = `${prefix}_${tenantId}`;
|
|
1062
|
-
return this.databaseCache.get(cacheKey);
|
|
1220
|
+
function sql_template_raw(value) {
|
|
1221
|
+
return {
|
|
1222
|
+
_isSqlFragment: true,
|
|
1223
|
+
sql: value,
|
|
1224
|
+
args: []
|
|
1225
|
+
};
|
|
1063
1226
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
* @param prefix - Database name prefix
|
|
1071
|
-
* @param tenantId - Tenant identifier
|
|
1072
|
-
* @param hash - Database hash
|
|
1073
|
-
*/ setCachedHash(prefix, tenantId, hash) {
|
|
1074
|
-
const cacheKey = `${prefix}_${tenantId}`;
|
|
1075
|
-
this.databaseCache.set(cacheKey, hash);
|
|
1227
|
+
function sql_template_fragment(strings, ...values) {
|
|
1228
|
+
const query = convertTemplateToQuery(strings, values);
|
|
1229
|
+
return {
|
|
1230
|
+
_isSqlFragment: true,
|
|
1231
|
+
...query
|
|
1232
|
+
};
|
|
1076
1233
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
this.config = config;
|
|
1234
|
+
function sql_template_join(fragments, separator = ', ') {
|
|
1235
|
+
if (0 === fragments.length) return empty();
|
|
1236
|
+
const sql = fragments.map((f)=>f.sql).join(separator);
|
|
1237
|
+
const args = fragments.flatMap((f)=>f.args);
|
|
1238
|
+
return {
|
|
1239
|
+
_isSqlFragment: true,
|
|
1240
|
+
sql,
|
|
1241
|
+
args
|
|
1242
|
+
};
|
|
1087
1243
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1244
|
+
function set(data, ...keys) {
|
|
1245
|
+
let columns;
|
|
1246
|
+
columns = keys.length > 0 ? keys : Object.keys(data);
|
|
1247
|
+
if (0 === columns.length) return empty();
|
|
1248
|
+
const setClauses = columns.map((key)=>`${key} = ?`);
|
|
1249
|
+
const values = columns.map((key)=>data[key]);
|
|
1250
|
+
return {
|
|
1251
|
+
_isSqlFragment: true,
|
|
1252
|
+
sql: setClauses.join(', '),
|
|
1253
|
+
args: values
|
|
1254
|
+
};
|
|
1094
1255
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1256
|
+
function sql_template_where(conditions) {
|
|
1257
|
+
const entries = Object.entries(conditions);
|
|
1258
|
+
if (0 === entries.length) return empty();
|
|
1259
|
+
const whereClauses = entries.map(([key])=>`${key} = ?`);
|
|
1260
|
+
const values = entries.map(([, value])=>value);
|
|
1261
|
+
return {
|
|
1262
|
+
_isSqlFragment: true,
|
|
1263
|
+
sql: whereClauses.join(' AND '),
|
|
1264
|
+
args: values
|
|
1265
|
+
};
|
|
1098
1266
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1267
|
+
class BunSQLiteAdapter {
|
|
1268
|
+
type = 'bun-sqlite';
|
|
1269
|
+
config;
|
|
1270
|
+
constructor(config){
|
|
1271
|
+
this.config = config;
|
|
1272
|
+
}
|
|
1273
|
+
async connect(config) {
|
|
1274
|
+
const db = new external_bun_sqlite_namespaceObject.Database(config.databasePath, {
|
|
1275
|
+
readonly: config.readonly,
|
|
1276
|
+
create: config.create ?? true
|
|
1104
1277
|
});
|
|
1105
|
-
|
|
1106
|
-
db.close();
|
|
1107
|
-
return true;
|
|
1108
|
-
} catch {
|
|
1109
|
-
return false;
|
|
1278
|
+
return new BunSQLiteConnection(db);
|
|
1110
1279
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
*/ async sql(strings, ...values) {
|
|
1124
|
-
// Build SQL from template
|
|
1125
|
-
const sqlStr = strings.reduce((acc, str, i)=>acc + str + (i < values.length ? '?' : ''), '');
|
|
1126
|
-
return this.execute(sqlStr, values);
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* Query with SQL string and parameters (alias for execute)
|
|
1130
|
-
*/ async query(sql, params = []) {
|
|
1131
|
-
return this.execute(sql, params);
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Execute SQL with parameters
|
|
1135
|
-
*/ async execute(sql, params = []) {
|
|
1136
|
-
try {
|
|
1137
|
-
// Handle object format { sql, args }
|
|
1138
|
-
let sqlStr;
|
|
1139
|
-
let sqlParams;
|
|
1140
|
-
if ('object' == typeof sql) {
|
|
1141
|
-
sqlStr = sql.sql;
|
|
1142
|
-
sqlParams = sql.args || [];
|
|
1143
|
-
} else {
|
|
1144
|
-
sqlStr = sql;
|
|
1145
|
-
sqlParams = params;
|
|
1146
|
-
}
|
|
1147
|
-
const stmt = this.db.query(sqlStr);
|
|
1148
|
-
const isSelect = sqlStr.trim().toUpperCase().startsWith('SELECT') || sqlStr.trim().toUpperCase().includes('RETURNING');
|
|
1149
|
-
if (isSelect) {
|
|
1150
|
-
const rows = stmt.all(...sqlParams);
|
|
1151
|
-
return {
|
|
1152
|
-
rows: rows,
|
|
1153
|
-
rowsAffected: 0
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
{
|
|
1157
|
-
const result = stmt.run(...sqlParams);
|
|
1158
|
-
return {
|
|
1159
|
-
rows: [],
|
|
1160
|
-
rowsAffected: result.changes || 0,
|
|
1161
|
-
lastInsertRowid: result.lastInsertRowid
|
|
1162
|
-
};
|
|
1280
|
+
async disconnect(tenantId) {}
|
|
1281
|
+
async isHealthy() {
|
|
1282
|
+
try {
|
|
1283
|
+
const db = new external_bun_sqlite_namespaceObject.Database(this.config.databasePath, {
|
|
1284
|
+
readonly: true,
|
|
1285
|
+
create: false
|
|
1286
|
+
});
|
|
1287
|
+
db.query('SELECT 1').get();
|
|
1288
|
+
db.close();
|
|
1289
|
+
return true;
|
|
1290
|
+
} catch {
|
|
1291
|
+
return false;
|
|
1163
1292
|
}
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
throw new Error(`SQL execution failed: ${error.message}`);
|
|
1166
1293
|
}
|
|
1167
1294
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1295
|
+
class BunSQLiteConnection {
|
|
1296
|
+
db;
|
|
1297
|
+
inTransaction = false;
|
|
1298
|
+
sql;
|
|
1299
|
+
constructor(db){
|
|
1300
|
+
this.db = db;
|
|
1301
|
+
const self = this;
|
|
1302
|
+
this.sql = function(stringsOrValue, ...values) {
|
|
1303
|
+
if (!stringsOrValue || 'object' != typeof stringsOrValue || !('raw' in stringsOrValue)) return sql_template_sql(stringsOrValue, ...values);
|
|
1304
|
+
{
|
|
1305
|
+
const query = convertTemplateToQuery(stringsOrValue, values);
|
|
1306
|
+
const thenableFragment = {
|
|
1307
|
+
_isSqlFragment: true,
|
|
1308
|
+
sql: query.sql,
|
|
1309
|
+
args: query.args
|
|
1310
|
+
};
|
|
1311
|
+
thenableFragment['then'] = function(onFulfilled, onRejected) {
|
|
1312
|
+
return self.execute(query).then((result)=>result.rows).then(onFulfilled, onRejected);
|
|
1313
|
+
};
|
|
1314
|
+
return thenableFragment;
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
this.sql.empty = empty;
|
|
1318
|
+
this.sql.raw = sql_template_raw;
|
|
1319
|
+
this.sql.fragment = sql_template_fragment;
|
|
1320
|
+
this.sql.join = sql_template_join;
|
|
1321
|
+
this.sql.set = set;
|
|
1322
|
+
this.sql.where = sql_template_where;
|
|
1323
|
+
}
|
|
1324
|
+
async query(sql, params = []) {
|
|
1325
|
+
return this.execute(sql, params);
|
|
1326
|
+
}
|
|
1327
|
+
async execute(sql, params = []) {
|
|
1328
|
+
try {
|
|
1329
|
+
let sqlStr;
|
|
1330
|
+
let sqlParams;
|
|
1331
|
+
if ('object' == typeof sql) {
|
|
1332
|
+
if (process.env.DEBUG_SQL) {
|
|
1333
|
+
console.log('[BunSQLite] Executing SQL:', sql.sql);
|
|
1334
|
+
console.log('[BunSQLite] With args:', sql.args);
|
|
1335
|
+
}
|
|
1336
|
+
sqlStr = sql.sql;
|
|
1337
|
+
sqlParams = sql.args || [];
|
|
1338
|
+
} else {
|
|
1339
|
+
if (process.env.DEBUG_SQL) {
|
|
1340
|
+
console.log('[BunSQLite] Executing SQL:', sql);
|
|
1341
|
+
console.log('[BunSQLite] With params:', params);
|
|
1342
|
+
}
|
|
1343
|
+
sqlStr = sql;
|
|
1344
|
+
sqlParams = params;
|
|
1345
|
+
}
|
|
1346
|
+
const stmt = this.db.query(sqlStr);
|
|
1347
|
+
const isSelect = sqlStr.trim().toUpperCase().startsWith('SELECT') || sqlStr.trim().toUpperCase().includes('RETURNING');
|
|
1175
1348
|
if (isSelect) {
|
|
1176
|
-
const rows = stmt.all(...
|
|
1349
|
+
const rows = stmt.all(...sqlParams);
|
|
1177
1350
|
return {
|
|
1178
1351
|
rows: rows,
|
|
1179
1352
|
rowsAffected: 0
|
|
1180
1353
|
};
|
|
1181
1354
|
}
|
|
1182
1355
|
{
|
|
1183
|
-
const result = stmt.run(...
|
|
1356
|
+
const result = stmt.run(...sqlParams);
|
|
1184
1357
|
return {
|
|
1185
1358
|
rows: [],
|
|
1186
1359
|
rowsAffected: result.changes || 0,
|
|
1187
1360
|
lastInsertRowid: result.lastInsertRowid
|
|
1188
1361
|
};
|
|
1189
1362
|
}
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
throw error;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
prepare(sql) {
|
|
1368
|
+
const stmt = this.db.query(sql);
|
|
1369
|
+
return {
|
|
1370
|
+
execute: async (params = [])=>{
|
|
1371
|
+
const isSelect = sql.trim().toUpperCase().startsWith('SELECT');
|
|
1372
|
+
if (isSelect) {
|
|
1373
|
+
const rows = stmt.all(...params);
|
|
1374
|
+
return {
|
|
1375
|
+
rows: rows,
|
|
1376
|
+
rowsAffected: 0
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
{
|
|
1380
|
+
const result = stmt.run(...params);
|
|
1381
|
+
return {
|
|
1382
|
+
rows: [],
|
|
1383
|
+
rowsAffected: result.changes || 0,
|
|
1384
|
+
lastInsertRowid: result.lastInsertRowid
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
},
|
|
1388
|
+
all: async (params = [])=>stmt.all(...params),
|
|
1389
|
+
get: async (params = [])=>stmt.get(...params)
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
async transaction(fn) {
|
|
1393
|
+
if (this.inTransaction) return fn(this);
|
|
1394
|
+
this.inTransaction = true;
|
|
1395
|
+
try {
|
|
1396
|
+
await this.execute('BEGIN');
|
|
1397
|
+
const result = await fn(this);
|
|
1398
|
+
await this.execute('COMMIT');
|
|
1399
|
+
return result;
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
await this.execute('ROLLBACK');
|
|
1402
|
+
throw error;
|
|
1403
|
+
} finally{
|
|
1404
|
+
this.inTransaction = false;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
async begin(fn) {
|
|
1408
|
+
return this.transaction(fn);
|
|
1409
|
+
}
|
|
1410
|
+
async close() {
|
|
1411
|
+
this.db.close();
|
|
1412
|
+
}
|
|
1413
|
+
createORM() {
|
|
1414
|
+
const { createORM } = __webpack_require__("./src/orm/index.ts");
|
|
1415
|
+
return createORM(this);
|
|
1211
1416
|
}
|
|
1212
1417
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
this.db.close();
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
const external_libsql_client_namespaceObject = require("@libsql/client");
|
|
1225
|
-
/**
|
|
1226
|
-
* LibSQL adapter for DatabaseManager
|
|
1227
|
-
* Wraps @libsql/client with Connection interface
|
|
1228
|
-
*/ class LibSQLAdapter {
|
|
1229
|
-
type = 'libsql';
|
|
1230
|
-
config;
|
|
1231
|
-
constructor(config){
|
|
1232
|
-
this.config = config;
|
|
1233
|
-
}
|
|
1234
|
-
async connect(config) {
|
|
1235
|
-
const client = (0, external_libsql_client_namespaceObject.createClient)({
|
|
1236
|
-
url: config.url,
|
|
1237
|
-
authToken: config.authToken,
|
|
1238
|
-
encryptionKey: config.encryptionKey
|
|
1239
|
-
});
|
|
1240
|
-
return new LibSQLConnection(client);
|
|
1241
|
-
}
|
|
1242
|
-
async disconnect(tenantId) {
|
|
1243
|
-
// LibSQL clients don't require explicit disconnection
|
|
1244
|
-
// Connections are pooled internally
|
|
1245
|
-
}
|
|
1246
|
-
async isHealthy() {
|
|
1247
|
-
try {
|
|
1418
|
+
const external_libsql_client_namespaceObject = require("@libsql/client");
|
|
1419
|
+
class LibSQLAdapter {
|
|
1420
|
+
type = 'libsql';
|
|
1421
|
+
config;
|
|
1422
|
+
constructor(config){
|
|
1423
|
+
this.config = config;
|
|
1424
|
+
}
|
|
1425
|
+
async connect(config) {
|
|
1248
1426
|
const client = (0, external_libsql_client_namespaceObject.createClient)({
|
|
1249
|
-
url:
|
|
1250
|
-
authToken:
|
|
1427
|
+
url: config.url,
|
|
1428
|
+
authToken: config.authToken,
|
|
1429
|
+
encryptionKey: config.encryptionKey
|
|
1251
1430
|
});
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1431
|
+
return new LibSQLConnection(client);
|
|
1432
|
+
}
|
|
1433
|
+
async disconnect(tenantId) {}
|
|
1434
|
+
async isHealthy() {
|
|
1435
|
+
try {
|
|
1436
|
+
const client = (0, external_libsql_client_namespaceObject.createClient)({
|
|
1437
|
+
url: this.config.url,
|
|
1438
|
+
authToken: this.config.authToken
|
|
1439
|
+
});
|
|
1440
|
+
await client.execute('SELECT 1');
|
|
1441
|
+
return true;
|
|
1442
|
+
} catch {
|
|
1443
|
+
return false;
|
|
1444
|
+
}
|
|
1256
1445
|
}
|
|
1257
1446
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
/**
|
|
1280
|
-
* Execute SQL with parameters
|
|
1281
|
-
* Supports both formats: execute(sql, params) and execute({sql, args})
|
|
1282
|
-
*/ async execute(sql, params = []) {
|
|
1283
|
-
try {
|
|
1284
|
-
const target = this.txClient || this.client;
|
|
1285
|
-
// Support both execute(sql, params) and execute({sql, args}) formats
|
|
1286
|
-
let query;
|
|
1287
|
-
query = 'string' == typeof sql ? {
|
|
1288
|
-
sql,
|
|
1289
|
-
args: params
|
|
1290
|
-
} : {
|
|
1291
|
-
sql: sql.sql,
|
|
1292
|
-
args: sql.args || []
|
|
1447
|
+
class LibSQLConnection {
|
|
1448
|
+
client;
|
|
1449
|
+
txClient;
|
|
1450
|
+
sql;
|
|
1451
|
+
constructor(client){
|
|
1452
|
+
this.client = client;
|
|
1453
|
+
const self = this;
|
|
1454
|
+
this.sql = function(stringsOrValue, ...values) {
|
|
1455
|
+
if (!stringsOrValue || 'object' != typeof stringsOrValue || !('raw' in stringsOrValue)) return sql_template_sql(stringsOrValue, ...values);
|
|
1456
|
+
{
|
|
1457
|
+
const query = convertTemplateToQuery(stringsOrValue, values);
|
|
1458
|
+
const thenableFragment = {
|
|
1459
|
+
_isSqlFragment: true,
|
|
1460
|
+
sql: query.sql,
|
|
1461
|
+
args: query.args
|
|
1462
|
+
};
|
|
1463
|
+
thenableFragment['then'] = function(onFulfilled, onRejected) {
|
|
1464
|
+
return self.execute(query).then((result)=>result.rows).then(onFulfilled, onRejected);
|
|
1465
|
+
};
|
|
1466
|
+
return thenableFragment;
|
|
1467
|
+
}
|
|
1293
1468
|
};
|
|
1294
|
-
|
|
1469
|
+
this.sql.empty = empty;
|
|
1470
|
+
this.sql.raw = sql_template_raw;
|
|
1471
|
+
this.sql.fragment = sql_template_fragment;
|
|
1472
|
+
this.sql.join = sql_template_join;
|
|
1473
|
+
this.sql.set = set;
|
|
1474
|
+
this.sql.where = sql_template_where;
|
|
1475
|
+
}
|
|
1476
|
+
async query(sql, params = []) {
|
|
1477
|
+
return this.execute(sql, params);
|
|
1478
|
+
}
|
|
1479
|
+
async execute(sql, params = []) {
|
|
1480
|
+
try {
|
|
1481
|
+
const target = this.txClient || this.client;
|
|
1482
|
+
let query;
|
|
1483
|
+
if ('string' == typeof sql) {
|
|
1484
|
+
if (process.env.DEBUG_SQL) {
|
|
1485
|
+
console.log('[LibSQL] Executing SQL:', sql);
|
|
1486
|
+
console.log('[LibSQL] With params:', params);
|
|
1487
|
+
}
|
|
1488
|
+
query = {
|
|
1489
|
+
sql,
|
|
1490
|
+
args: params
|
|
1491
|
+
};
|
|
1492
|
+
} else {
|
|
1493
|
+
if (process.env.DEBUG_SQL) {
|
|
1494
|
+
console.log('[LibSQL] Executing SQL:', sql.sql);
|
|
1495
|
+
console.log('[LibSQL] With args:', sql.args);
|
|
1496
|
+
}
|
|
1497
|
+
query = {
|
|
1498
|
+
sql: sql.sql,
|
|
1499
|
+
args: sql.args || []
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
const result = await target.execute(query);
|
|
1503
|
+
return {
|
|
1504
|
+
rows: result.rows,
|
|
1505
|
+
rowsAffected: Number(result.rowsAffected),
|
|
1506
|
+
lastInsertRowid: result.lastInsertRowid ? BigInt(result.lastInsertRowid.toString()) : void 0
|
|
1507
|
+
};
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
throw error;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
prepare(sql) {
|
|
1295
1513
|
return {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1514
|
+
execute: async (params = [])=>this.execute(sql, params),
|
|
1515
|
+
all: async (params = [])=>{
|
|
1516
|
+
const result = await this.execute(sql, params);
|
|
1517
|
+
return result.rows;
|
|
1518
|
+
},
|
|
1519
|
+
get: async (params = [])=>{
|
|
1520
|
+
const result = await this.execute(sql, params);
|
|
1521
|
+
return result.rows[0] || null;
|
|
1522
|
+
}
|
|
1299
1523
|
};
|
|
1300
|
-
} catch (error) {
|
|
1301
|
-
throw new Error(`SQL execution failed: ${error.message}`);
|
|
1302
1524
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
get: async (params = [])=>{
|
|
1314
|
-
const result = await this.execute(sql, params);
|
|
1315
|
-
return result.rows[0] || null;
|
|
1525
|
+
async transaction(fn) {
|
|
1526
|
+
if (this.txClient) return fn(this);
|
|
1527
|
+
try {
|
|
1528
|
+
await this.execute('BEGIN');
|
|
1529
|
+
const result = await fn(this);
|
|
1530
|
+
await this.execute('COMMIT');
|
|
1531
|
+
return result;
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
await this.execute('ROLLBACK');
|
|
1534
|
+
throw error;
|
|
1316
1535
|
}
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
try {
|
|
1326
|
-
await this.execute('BEGIN');
|
|
1327
|
-
const result = await fn(this);
|
|
1328
|
-
await this.execute('COMMIT');
|
|
1329
|
-
return result;
|
|
1330
|
-
} catch (error) {
|
|
1331
|
-
await this.execute('ROLLBACK');
|
|
1332
|
-
throw error;
|
|
1536
|
+
}
|
|
1537
|
+
async begin(fn) {
|
|
1538
|
+
return this.transaction(fn);
|
|
1539
|
+
}
|
|
1540
|
+
async close() {}
|
|
1541
|
+
createORM() {
|
|
1542
|
+
const { createORM } = __webpack_require__("./src/orm/index.ts");
|
|
1543
|
+
return createORM(this);
|
|
1333
1544
|
}
|
|
1334
1545
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
async disconnect(tenantId) {
|
|
1379
|
-
if (tenantId) {
|
|
1380
|
-
const prefix = 'pipeline_tenant_' // TODO: make configurable
|
|
1381
|
-
;
|
|
1382
|
-
const databaseName = `${prefix}${tenantId}`;
|
|
1546
|
+
class ODBLiteAdapter {
|
|
1547
|
+
type = 'odblite';
|
|
1548
|
+
config;
|
|
1549
|
+
serviceClient;
|
|
1550
|
+
constructor(config){
|
|
1551
|
+
this.config = config;
|
|
1552
|
+
this.serviceClient = new ServiceClient({
|
|
1553
|
+
baseUrl: config.serviceUrl,
|
|
1554
|
+
apiKey: config.apiKey
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
async connect(config) {
|
|
1558
|
+
const databaseName = config.databaseName || 'default';
|
|
1559
|
+
const databaseHash = config.databaseHash;
|
|
1560
|
+
if (databaseHash) {
|
|
1561
|
+
const client = new ODBLiteClient({
|
|
1562
|
+
baseUrl: this.config.serviceUrl,
|
|
1563
|
+
apiKey: this.config.apiKey,
|
|
1564
|
+
databaseId: databaseHash
|
|
1565
|
+
});
|
|
1566
|
+
return new ODBLiteConnection(client, this.serviceClient, databaseName, databaseHash);
|
|
1567
|
+
}
|
|
1568
|
+
const db = await this.serviceClient.getDatabaseByName(databaseName);
|
|
1569
|
+
if (!db) throw new Error(`Database ${databaseName} not found. Please create it first using the admin API.`);
|
|
1570
|
+
const client = new ODBLiteClient({
|
|
1571
|
+
baseUrl: this.config.serviceUrl,
|
|
1572
|
+
apiKey: this.config.apiKey,
|
|
1573
|
+
databaseId: db.hash
|
|
1574
|
+
});
|
|
1575
|
+
return new ODBLiteConnection(client, this.serviceClient, databaseName, db.hash);
|
|
1576
|
+
}
|
|
1577
|
+
async disconnect(tenantId) {
|
|
1578
|
+
if (tenantId) {
|
|
1579
|
+
const prefix = 'pipeline_tenant_';
|
|
1580
|
+
const databaseName = `${prefix}${tenantId}`;
|
|
1581
|
+
try {
|
|
1582
|
+
await this.serviceClient.deleteDatabase(databaseName);
|
|
1583
|
+
} catch (error) {
|
|
1584
|
+
console.warn(`Failed to delete database ${databaseName}:`, error.message);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
async isHealthy() {
|
|
1383
1589
|
try {
|
|
1384
|
-
await this.serviceClient.
|
|
1385
|
-
|
|
1386
|
-
|
|
1590
|
+
await this.serviceClient.listDatabases();
|
|
1591
|
+
return true;
|
|
1592
|
+
} catch {
|
|
1593
|
+
return false;
|
|
1387
1594
|
}
|
|
1388
1595
|
}
|
|
1389
1596
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1597
|
+
class ODBLiteConnection {
|
|
1598
|
+
client;
|
|
1599
|
+
serviceClient;
|
|
1600
|
+
inTransaction = false;
|
|
1601
|
+
databaseName;
|
|
1602
|
+
databaseHash;
|
|
1603
|
+
sql;
|
|
1604
|
+
constructor(client, serviceClient, databaseName, databaseHash){
|
|
1605
|
+
this.client = client;
|
|
1606
|
+
this.serviceClient = serviceClient;
|
|
1607
|
+
this.databaseName = databaseName;
|
|
1608
|
+
this.databaseHash = databaseHash;
|
|
1609
|
+
const self = this;
|
|
1610
|
+
this.sql = function(stringsOrValue, ...values) {
|
|
1611
|
+
if (!stringsOrValue || 'object' != typeof stringsOrValue || !('raw' in stringsOrValue)) return sql_template_sql(stringsOrValue, ...values);
|
|
1612
|
+
{
|
|
1613
|
+
const query = convertTemplateToQuery(stringsOrValue, values);
|
|
1614
|
+
const thenableFragment = {
|
|
1615
|
+
_isSqlFragment: true,
|
|
1616
|
+
sql: query.sql,
|
|
1617
|
+
args: query.args
|
|
1618
|
+
};
|
|
1619
|
+
thenableFragment['then'] = function(onFulfilled, onRejected) {
|
|
1620
|
+
return self.execute(query).then((result)=>result.rows).then(onFulfilled, onRejected);
|
|
1621
|
+
};
|
|
1622
|
+
return thenableFragment;
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
this.sql.empty = empty;
|
|
1626
|
+
this.sql.raw = sql_template_raw;
|
|
1627
|
+
this.sql.fragment = sql_template_fragment;
|
|
1628
|
+
this.sql.join = sql_template_join;
|
|
1629
|
+
this.sql.set = set;
|
|
1630
|
+
this.sql.where = sql_template_where;
|
|
1396
1631
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
};
|
|
1420
|
-
}
|
|
1421
|
-
/**
|
|
1422
|
-
* Query with SQL string and parameters (alias for execute)
|
|
1423
|
-
*/ async query(sql, params = []) {
|
|
1424
|
-
return this.execute(sql, params);
|
|
1425
|
-
}
|
|
1426
|
-
/**
|
|
1427
|
-
* Execute SQL with parameters
|
|
1428
|
-
*/ async execute(sql, params = []) {
|
|
1429
|
-
try {
|
|
1430
|
-
// Handle object format { sql, args }
|
|
1431
|
-
if ('object' == typeof sql) {
|
|
1432
|
-
const result = await this.client.sql.execute(sql.sql, sql.args || []);
|
|
1632
|
+
async query(sql, params = []) {
|
|
1633
|
+
return this.execute(sql, params);
|
|
1634
|
+
}
|
|
1635
|
+
async execute(sql, params = []) {
|
|
1636
|
+
try {
|
|
1637
|
+
if ('object' == typeof sql) {
|
|
1638
|
+
if (process.env.DEBUG_SQL) {
|
|
1639
|
+
console.log('[ODBLite] Executing SQL:', sql.sql);
|
|
1640
|
+
console.log('[ODBLite] With args:', sql.args);
|
|
1641
|
+
}
|
|
1642
|
+
const result = await this.client.sql.execute(sql.sql, sql.args || []);
|
|
1643
|
+
return {
|
|
1644
|
+
rows: result.rows,
|
|
1645
|
+
rowsAffected: result.rowsAffected || 0,
|
|
1646
|
+
lastInsertRowid: result.lastInsertRowid
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
if (process.env.DEBUG_SQL) {
|
|
1650
|
+
console.log('[ODBLite] Executing SQL:', sql);
|
|
1651
|
+
console.log('[ODBLite] With params:', params);
|
|
1652
|
+
}
|
|
1653
|
+
const result = await this.client.sql.execute(sql, params);
|
|
1433
1654
|
return {
|
|
1434
1655
|
rows: result.rows,
|
|
1435
|
-
rowsAffected: result.rowsAffected || 0
|
|
1656
|
+
rowsAffected: result.rowsAffected || 0,
|
|
1657
|
+
lastInsertRowid: result.lastInsertRowid
|
|
1436
1658
|
};
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
throw error;
|
|
1437
1661
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1662
|
+
}
|
|
1663
|
+
prepare(sql) {
|
|
1440
1664
|
return {
|
|
1441
|
-
|
|
1442
|
-
|
|
1665
|
+
execute: async (params = [])=>this.execute(sql, params),
|
|
1666
|
+
all: async (params = [])=>{
|
|
1667
|
+
const result = await this.execute(sql, params);
|
|
1668
|
+
return result.rows;
|
|
1669
|
+
},
|
|
1670
|
+
get: async (params = [])=>{
|
|
1671
|
+
const result = await this.execute(sql, params);
|
|
1672
|
+
return result.rows[0] || null;
|
|
1673
|
+
}
|
|
1443
1674
|
};
|
|
1444
|
-
} catch (error) {
|
|
1445
|
-
throw new Error(`SQL execution failed: ${error.message}`);
|
|
1446
1675
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1676
|
+
async transaction(fn) {
|
|
1677
|
+
if (this.inTransaction) return fn(this);
|
|
1678
|
+
this.inTransaction = true;
|
|
1679
|
+
try {
|
|
1680
|
+
await this.execute('BEGIN');
|
|
1681
|
+
const result = await fn(this);
|
|
1682
|
+
await this.execute('COMMIT');
|
|
1683
|
+
return result;
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
await this.execute('ROLLBACK');
|
|
1686
|
+
throw error;
|
|
1687
|
+
} finally{
|
|
1688
|
+
this.inTransaction = false;
|
|
1460
1689
|
}
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
try {
|
|
1470
|
-
await this.execute('BEGIN');
|
|
1471
|
-
const result = await fn(this);
|
|
1472
|
-
await this.execute('COMMIT');
|
|
1473
|
-
return result;
|
|
1474
|
-
} catch (error) {
|
|
1475
|
-
await this.execute('ROLLBACK');
|
|
1476
|
-
throw error;
|
|
1477
|
-
} finally{
|
|
1478
|
-
this.inTransaction = false;
|
|
1690
|
+
}
|
|
1691
|
+
async begin(fn) {
|
|
1692
|
+
return this.transaction(fn);
|
|
1693
|
+
}
|
|
1694
|
+
async close() {}
|
|
1695
|
+
createORM() {
|
|
1696
|
+
const { createORM } = __webpack_require__("./src/orm/index.ts");
|
|
1697
|
+
return createORM(this);
|
|
1479
1698
|
}
|
|
1480
1699
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
const statements = splitStatements(sqlContent);
|
|
1500
|
-
if (!separatePragma) return {
|
|
1501
|
-
pragmaStatements: [],
|
|
1502
|
-
regularStatements: statements
|
|
1503
|
-
};
|
|
1504
|
-
const pragmaStatements = [];
|
|
1505
|
-
const regularStatements = [];
|
|
1506
|
-
for (const statement of statements){
|
|
1507
|
-
const trimmed = statement.trim().toUpperCase();
|
|
1508
|
-
if (trimmed.startsWith('PRAGMA')) pragmaStatements.push(statement);
|
|
1509
|
-
else regularStatements.push(statement);
|
|
1700
|
+
function parseSQL(sqlContent, options = {}) {
|
|
1701
|
+
const separatePragma = options.separatePragma ?? true;
|
|
1702
|
+
const statements = splitStatements(sqlContent);
|
|
1703
|
+
if (!separatePragma) return {
|
|
1704
|
+
pragmaStatements: [],
|
|
1705
|
+
regularStatements: statements
|
|
1706
|
+
};
|
|
1707
|
+
const pragmaStatements = [];
|
|
1708
|
+
const regularStatements = [];
|
|
1709
|
+
for (const statement of statements){
|
|
1710
|
+
const trimmed = statement.trim().toUpperCase();
|
|
1711
|
+
if (trimmed.startsWith('PRAGMA')) pragmaStatements.push(statement);
|
|
1712
|
+
else regularStatements.push(statement);
|
|
1713
|
+
}
|
|
1714
|
+
return {
|
|
1715
|
+
pragmaStatements,
|
|
1716
|
+
regularStatements
|
|
1717
|
+
};
|
|
1510
1718
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
if (beginEndDepth < 0) beginEndDepth = 0;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
for(let i = 0; i < line.length; i++){
|
|
1540
|
-
const char = line[i];
|
|
1541
|
-
const prevChar = i > 0 ? line[i - 1] : '';
|
|
1542
|
-
const nextChar = i < line.length - 1 ? line[i + 1] : '';
|
|
1543
|
-
// Handle single-line comments
|
|
1544
|
-
if (!inQuote && '-' === char && '-' === nextChar) break; // Skip rest of line
|
|
1545
|
-
// Track quotes
|
|
1546
|
-
if (("'" === char || '"' === char) && '\\' !== prevChar) {
|
|
1547
|
-
if (inQuote) {
|
|
1719
|
+
function splitStatements(sqlContent) {
|
|
1720
|
+
const statements = [];
|
|
1721
|
+
let currentStatement = '';
|
|
1722
|
+
let inQuote = false;
|
|
1723
|
+
let quoteChar = '';
|
|
1724
|
+
let beginEndDepth = 0;
|
|
1725
|
+
const lines = sqlContent.split('\n');
|
|
1726
|
+
for (const line of lines){
|
|
1727
|
+
let processedLine = '';
|
|
1728
|
+
const lineWithoutComments = line.replace(/--.*$/, '');
|
|
1729
|
+
if (!inQuote && lineWithoutComments.trim()) {
|
|
1730
|
+
const beginMatches = lineWithoutComments.match(/\bBEGIN\b/gi);
|
|
1731
|
+
const endMatches = lineWithoutComments.match(/\bEND\b/gi);
|
|
1732
|
+
if (beginMatches) beginEndDepth += beginMatches.length;
|
|
1733
|
+
if (endMatches) {
|
|
1734
|
+
beginEndDepth -= endMatches.length;
|
|
1735
|
+
if (beginEndDepth < 0) beginEndDepth = 0;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
for(let i = 0; i < line.length; i++){
|
|
1739
|
+
const char = line[i];
|
|
1740
|
+
const prevChar = i > 0 ? line[i - 1] : '';
|
|
1741
|
+
const nextChar = i < line.length - 1 ? line[i + 1] : '';
|
|
1742
|
+
if (!inQuote && '-' === char && '-' === nextChar) break;
|
|
1743
|
+
if (("'" === char || '"' === char) && '\\' !== prevChar) if (inQuote) {
|
|
1548
1744
|
if (char === quoteChar) {
|
|
1549
1745
|
inQuote = false;
|
|
1550
1746
|
quoteChar = '';
|
|
@@ -1553,289 +1749,291 @@ const external_libsql_client_namespaceObject = require("@libsql/client");
|
|
|
1553
1749
|
inQuote = true;
|
|
1554
1750
|
quoteChar = char;
|
|
1555
1751
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
currentStatement = '';
|
|
1564
|
-
processedLine = '';
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
if (processedLine.trim()) currentStatement += `${processedLine}\n`;
|
|
1568
|
-
}
|
|
1569
|
-
// Handle remaining statement
|
|
1570
|
-
const finalStmt = currentStatement.trim();
|
|
1571
|
-
if (finalStmt && !finalStmt.startsWith('--')) statements.push(finalStmt);
|
|
1572
|
-
return statements;
|
|
1573
|
-
}
|
|
1574
|
-
/**
|
|
1575
|
-
* Simple split for systems that don't need PRAGMA separation
|
|
1576
|
-
*/ function splitSQLStatements(sqlContent) {
|
|
1577
|
-
const { pragmaStatements, regularStatements } = parseSQL(sqlContent);
|
|
1578
|
-
return [
|
|
1579
|
-
...pragmaStatements,
|
|
1580
|
-
...regularStatements
|
|
1581
|
-
];
|
|
1582
|
-
}
|
|
1583
|
-
const external_node_fs_namespaceObject = require("node:fs");
|
|
1584
|
-
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
1585
|
-
/**
|
|
1586
|
-
* Database factory for creating and managing multiple databases
|
|
1587
|
-
*
|
|
1588
|
-
* Features:
|
|
1589
|
-
* - Creates multiple databases from single manager instance
|
|
1590
|
-
* - Supports libsql, bun:sqlite, and ODB-Lite backends
|
|
1591
|
-
* - Connection pooling with LRU eviction
|
|
1592
|
-
* - Automatic schema migrations
|
|
1593
|
-
*
|
|
1594
|
-
* @example
|
|
1595
|
-
* ```ts
|
|
1596
|
-
* const dbManager = new DatabaseManager({
|
|
1597
|
-
* backend: 'libsql',
|
|
1598
|
-
* databasePath: './data'
|
|
1599
|
-
* })
|
|
1600
|
-
*
|
|
1601
|
-
* // Create databases
|
|
1602
|
-
* await dbManager.createDatabase('identity', { schemaContent })
|
|
1603
|
-
* await dbManager.createDatabase('analytics', { schemaContent })
|
|
1604
|
-
*
|
|
1605
|
-
* // Get connections
|
|
1606
|
-
* const identityDB = await dbManager.getConnection('identity')
|
|
1607
|
-
* const analyticsDB = await dbManager.getConnection('analytics')
|
|
1608
|
-
* ```
|
|
1609
|
-
*/ class DatabaseManager {
|
|
1610
|
-
config;
|
|
1611
|
-
adapter;
|
|
1612
|
-
connections = new Map();
|
|
1613
|
-
// Connection pool management (LRU)
|
|
1614
|
-
connectionTimestamps = new Map();
|
|
1615
|
-
maxConnections;
|
|
1616
|
-
constructor(config){
|
|
1617
|
-
this.config = config;
|
|
1618
|
-
this.maxConnections = config.connectionPoolSize || 10;
|
|
1619
|
-
// Create appropriate adapter based on backend
|
|
1620
|
-
switch(config.backend){
|
|
1621
|
-
case 'bun-sqlite':
|
|
1622
|
-
this.adapter = new BunSQLiteAdapter({
|
|
1623
|
-
databasePath: config.databasePath,
|
|
1624
|
-
create: true
|
|
1625
|
-
});
|
|
1626
|
-
break;
|
|
1627
|
-
case 'libsql':
|
|
1628
|
-
this.adapter = new LibSQLAdapter({
|
|
1629
|
-
url: `file:${config.databasePath}`,
|
|
1630
|
-
...config.libsql
|
|
1631
|
-
});
|
|
1632
|
-
break;
|
|
1633
|
-
case 'odblite':
|
|
1634
|
-
if (!config.odblite) throw new Error('odblite config required for odblite backend');
|
|
1635
|
-
this.adapter = new ODBLiteAdapter(config.odblite);
|
|
1636
|
-
break;
|
|
1637
|
-
default:
|
|
1638
|
-
throw new Error(`Unknown backend type: ${config.backend}`);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
/**
|
|
1642
|
-
* Create a new database
|
|
1643
|
-
* @param name - Database name (becomes filename or ODB-Lite database name)
|
|
1644
|
-
* @param options - Optional creation options
|
|
1645
|
-
*/ async createDatabase(name, options) {
|
|
1646
|
-
console.log(`📦 Creating database: ${name}`);
|
|
1647
|
-
// Build database-specific config
|
|
1648
|
-
const dbConfig = this.getDatabaseConfig(name);
|
|
1649
|
-
// Create connection
|
|
1650
|
-
const conn = await this.adapter.connect(dbConfig);
|
|
1651
|
-
// Run migrations if schema provided
|
|
1652
|
-
if (options?.schemaContent) await this.runMigrations(conn, {
|
|
1653
|
-
schemaContent: options.schemaContent
|
|
1654
|
-
});
|
|
1655
|
-
// Store connection
|
|
1656
|
-
this.connections.set(name, conn);
|
|
1657
|
-
this.connectionTimestamps.set(name, Date.now());
|
|
1658
|
-
// Evict LRU if pool is full
|
|
1659
|
-
if (this.connections.size > this.maxConnections) await this.evictLRU();
|
|
1660
|
-
console.log(`✅ Database created: ${name}`);
|
|
1661
|
-
}
|
|
1662
|
-
/**
|
|
1663
|
-
* Check if a database exists
|
|
1664
|
-
* @param name - Database name
|
|
1665
|
-
* @returns true if database exists, false otherwise
|
|
1666
|
-
*
|
|
1667
|
-
* Note: For local backends (libsql, bun-sqlite), checks file existence.
|
|
1668
|
-
* For ODB-Lite, checks if database is in connection cache.
|
|
1669
|
-
*/ async databaseExists(name) {
|
|
1670
|
-
// Check if already in connection cache
|
|
1671
|
-
if (this.connections.has(name)) return true;
|
|
1672
|
-
// For local file-based backends, check if file exists
|
|
1673
|
-
switch(this.config.backend){
|
|
1674
|
-
case 'bun-sqlite':
|
|
1675
|
-
{
|
|
1676
|
-
const dbPath = `${this.config.databasePath}/${name}.db`;
|
|
1677
|
-
return external_node_fs_default().existsSync(dbPath);
|
|
1678
|
-
}
|
|
1679
|
-
case 'libsql':
|
|
1680
|
-
{
|
|
1681
|
-
const dbPath = `${this.config.databasePath}/${name}.db`;
|
|
1682
|
-
return external_node_fs_default().existsSync(dbPath);
|
|
1752
|
+
processedLine += char;
|
|
1753
|
+
if (';' === char && !inQuote && 0 === beginEndDepth) {
|
|
1754
|
+
currentStatement += processedLine;
|
|
1755
|
+
const stmt = currentStatement.trim();
|
|
1756
|
+
if (stmt && !stmt.startsWith('--')) statements.push(stmt);
|
|
1757
|
+
currentStatement = '';
|
|
1758
|
+
processedLine = '';
|
|
1683
1759
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
// (no way to check remote database existence without connecting)
|
|
1687
|
-
return false;
|
|
1688
|
-
default:
|
|
1689
|
-
return false;
|
|
1760
|
+
}
|
|
1761
|
+
if (processedLine.trim()) currentStatement += `${processedLine}\n`;
|
|
1690
1762
|
}
|
|
1763
|
+
const finalStmt = currentStatement.trim();
|
|
1764
|
+
if (finalStmt && !finalStmt.startsWith('--')) statements.push(finalStmt);
|
|
1765
|
+
return statements;
|
|
1766
|
+
}
|
|
1767
|
+
function splitSQLStatements(sqlContent) {
|
|
1768
|
+
const { pragmaStatements, regularStatements } = parseSQL(sqlContent);
|
|
1769
|
+
return [
|
|
1770
|
+
...pragmaStatements,
|
|
1771
|
+
...regularStatements
|
|
1772
|
+
];
|
|
1691
1773
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
this.
|
|
1703
|
-
|
|
1774
|
+
const external_node_fs_namespaceObject = require("node:fs");
|
|
1775
|
+
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
1776
|
+
class DatabaseManager {
|
|
1777
|
+
config;
|
|
1778
|
+
adapter;
|
|
1779
|
+
connections = new Map();
|
|
1780
|
+
connectionTimestamps = new Map();
|
|
1781
|
+
maxConnections;
|
|
1782
|
+
constructor(config){
|
|
1783
|
+
this.config = config;
|
|
1784
|
+
this.maxConnections = config.connectionPoolSize || 10;
|
|
1785
|
+
switch(config.backend){
|
|
1786
|
+
case 'bun-sqlite':
|
|
1787
|
+
this.adapter = new BunSQLiteAdapter({
|
|
1788
|
+
databasePath: config.databasePath,
|
|
1789
|
+
create: true
|
|
1790
|
+
});
|
|
1791
|
+
break;
|
|
1792
|
+
case 'libsql':
|
|
1793
|
+
this.adapter = new LibSQLAdapter({
|
|
1794
|
+
url: `file:${config.databasePath}`,
|
|
1795
|
+
...config.libsql
|
|
1796
|
+
});
|
|
1797
|
+
break;
|
|
1798
|
+
case 'odblite':
|
|
1799
|
+
if (!config.odblite) throw new Error('odblite config required for odblite backend');
|
|
1800
|
+
this.adapter = new ODBLiteAdapter(config.odblite);
|
|
1801
|
+
break;
|
|
1802
|
+
default:
|
|
1803
|
+
throw new Error(`Unknown backend type: ${config.backend}`);
|
|
1804
|
+
}
|
|
1704
1805
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
if (exists) {
|
|
1708
|
-
// Connect to existing database
|
|
1806
|
+
async createDatabase(name, options) {
|
|
1807
|
+
console.log(`📦 Creating database: ${name}`);
|
|
1709
1808
|
const dbConfig = this.getDatabaseConfig(name);
|
|
1710
|
-
conn = await this.adapter.connect(dbConfig);
|
|
1711
|
-
|
|
1809
|
+
const conn = await this.adapter.connect(dbConfig);
|
|
1810
|
+
if (options?.schemaContent) await this.runMigrations(conn, {
|
|
1811
|
+
schemaContent: options.schemaContent
|
|
1812
|
+
});
|
|
1712
1813
|
this.connections.set(name, conn);
|
|
1713
1814
|
this.connectionTimestamps.set(name, Date.now());
|
|
1714
|
-
// Evict LRU if pool is full
|
|
1715
1815
|
if (this.connections.size > this.maxConnections) await this.evictLRU();
|
|
1816
|
+
console.log(`✅ Database created: ${name}`);
|
|
1716
1817
|
return conn;
|
|
1717
1818
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
}
|
|
1737
|
-
/**
|
|
1738
|
-
* Execute SQL content on a database connection
|
|
1739
|
-
* Useful for running migrations or initial schema
|
|
1740
|
-
* @param name - Database name
|
|
1741
|
-
* @param sqlContent - SQL content to execute
|
|
1742
|
-
*/ async executeSQLFile(name, sqlContent) {
|
|
1743
|
-
const conn = await this.getConnection(name);
|
|
1744
|
-
await this.runMigrations(conn, {
|
|
1745
|
-
schemaContent: sqlContent
|
|
1746
|
-
});
|
|
1747
|
-
}
|
|
1748
|
-
/**
|
|
1749
|
-
* List all managed databases
|
|
1750
|
-
*/ async listDatabases() {
|
|
1751
|
-
return Array.from(this.connections.keys());
|
|
1752
|
-
}
|
|
1753
|
-
/**
|
|
1754
|
-
* Close all connections
|
|
1755
|
-
*/ async close() {
|
|
1756
|
-
console.log(`🔌 Closing all database connections...`);
|
|
1757
|
-
for (const [name, conn] of this.connections)await conn.close();
|
|
1758
|
-
this.connections.clear();
|
|
1759
|
-
this.connectionTimestamps.clear();
|
|
1760
|
-
console.log(`✅ All connections closed`);
|
|
1761
|
-
}
|
|
1762
|
-
/**
|
|
1763
|
-
* Run migrations on a connection
|
|
1764
|
-
*/ async runMigrations(conn, config) {
|
|
1765
|
-
// Parse SQL statements
|
|
1766
|
-
const { pragmaStatements, regularStatements } = parseSQL(config.schemaContent);
|
|
1767
|
-
// Execute PRAGMA statements first
|
|
1768
|
-
for (const pragma of pragmaStatements)try {
|
|
1769
|
-
await conn.execute(pragma);
|
|
1770
|
-
} catch (error) {
|
|
1771
|
-
// Ignore PRAGMA errors (they might not be supported on all backends)
|
|
1772
|
-
console.log(`⚠️ PRAGMA note: ${error.message}`);
|
|
1773
|
-
}
|
|
1774
|
-
// Execute regular statements
|
|
1775
|
-
for (const statement of regularStatements)try {
|
|
1776
|
-
await conn.execute(statement);
|
|
1777
|
-
} catch (error) {
|
|
1778
|
-
// Ignore "already exists" errors for idempotency
|
|
1779
|
-
if (error.message?.includes('already exists') || error.message?.includes('no such column') || error.message?.includes('no such table')) {
|
|
1780
|
-
console.log(` ⚠️ Skipping: ${error.message}`);
|
|
1781
|
-
continue;
|
|
1782
|
-
}
|
|
1783
|
-
console.error(`❌ Migration failed: ${statement.substring(0, 100)}...`);
|
|
1784
|
-
throw error;
|
|
1785
|
-
}
|
|
1786
|
-
console.log(`✅ Migrations completed (${regularStatements.length} statements)`);
|
|
1787
|
-
}
|
|
1788
|
-
/**
|
|
1789
|
-
* Get database-specific configuration
|
|
1790
|
-
*/ getDatabaseConfig(name) {
|
|
1791
|
-
switch(this.config.backend){
|
|
1792
|
-
case 'bun-sqlite':
|
|
1793
|
-
return {
|
|
1794
|
-
databasePath: `${this.config.databasePath}/${name}.db`,
|
|
1795
|
-
create: true
|
|
1796
|
-
};
|
|
1797
|
-
case 'libsql':
|
|
1798
|
-
return {
|
|
1799
|
-
url: `file:${this.config.databasePath}/${name}.db`,
|
|
1800
|
-
...this.config.libsql
|
|
1801
|
-
};
|
|
1802
|
-
case 'odblite':
|
|
1803
|
-
return {
|
|
1804
|
-
databaseName: `${this.config.databasePath}_${name}`,
|
|
1805
|
-
...this.config.odblite
|
|
1806
|
-
};
|
|
1807
|
-
default:
|
|
1808
|
-
throw new Error(`Unknown backend: ${this.config.backend}`);
|
|
1819
|
+
async databaseExists(name) {
|
|
1820
|
+
if (this.connections.has(name)) return true;
|
|
1821
|
+
switch(this.config.backend){
|
|
1822
|
+
case 'bun-sqlite':
|
|
1823
|
+
{
|
|
1824
|
+
const dbPath = `${this.config.databasePath}/${name}.db`;
|
|
1825
|
+
return external_node_fs_default().existsSync(dbPath);
|
|
1826
|
+
}
|
|
1827
|
+
case 'libsql':
|
|
1828
|
+
{
|
|
1829
|
+
const dbPath = `${this.config.databasePath}/${name}.db`;
|
|
1830
|
+
return external_node_fs_default().existsSync(dbPath);
|
|
1831
|
+
}
|
|
1832
|
+
case 'odblite':
|
|
1833
|
+
return false;
|
|
1834
|
+
default:
|
|
1835
|
+
return false;
|
|
1836
|
+
}
|
|
1809
1837
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1838
|
+
async getConnection(name) {
|
|
1839
|
+
let conn = this.connections.get(name);
|
|
1840
|
+
if (conn) {
|
|
1841
|
+
this.connectionTimestamps.set(name, Date.now());
|
|
1842
|
+
return conn;
|
|
1843
|
+
}
|
|
1844
|
+
const exists = await this.databaseExists(name);
|
|
1845
|
+
if (exists) {
|
|
1846
|
+
const dbConfig = this.getDatabaseConfig(name);
|
|
1847
|
+
conn = await this.adapter.connect(dbConfig);
|
|
1848
|
+
this.connections.set(name, conn);
|
|
1849
|
+
this.connectionTimestamps.set(name, Date.now());
|
|
1850
|
+
if (this.connections.size > this.maxConnections) await this.evictLRU();
|
|
1851
|
+
return conn;
|
|
1852
|
+
}
|
|
1853
|
+
throw new Error(`Database "${name}" not found. Call createDatabase("${name}", { schemaContent }) first.`);
|
|
1854
|
+
}
|
|
1855
|
+
async deleteDatabase(name) {
|
|
1856
|
+
console.log(`🗑️ Deleting database: ${name}`);
|
|
1857
|
+
const conn = this.connections.get(name);
|
|
1858
|
+
if (conn) {
|
|
1859
|
+
await conn.close();
|
|
1860
|
+
this.connections.delete(name);
|
|
1861
|
+
this.connectionTimestamps.delete(name);
|
|
1862
|
+
}
|
|
1863
|
+
await this.adapter.disconnect(name);
|
|
1864
|
+
console.log(`✅ Database deleted: ${name}`);
|
|
1865
|
+
}
|
|
1866
|
+
async executeSQLFile(name, sqlContent) {
|
|
1867
|
+
const conn = await this.getConnection(name);
|
|
1868
|
+
await this.runMigrations(conn, {
|
|
1869
|
+
schemaContent: sqlContent
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
async listDatabases() {
|
|
1873
|
+
return Array.from(this.connections.keys());
|
|
1874
|
+
}
|
|
1875
|
+
async close() {
|
|
1876
|
+
console.log(`🔌 Closing all database connections...`);
|
|
1877
|
+
for (const [name, conn] of this.connections)await conn.close();
|
|
1878
|
+
this.connections.clear();
|
|
1879
|
+
this.connectionTimestamps.clear();
|
|
1880
|
+
console.log(`✅ All connections closed`);
|
|
1881
|
+
}
|
|
1882
|
+
async runMigrations(conn, config) {
|
|
1883
|
+
const { pragmaStatements, regularStatements } = parseSQL(config.schemaContent);
|
|
1884
|
+
for (const pragma of pragmaStatements)try {
|
|
1885
|
+
await conn.execute(pragma);
|
|
1886
|
+
} catch (error) {
|
|
1887
|
+
console.log(`⚠️ PRAGMA note: ${error.message}`);
|
|
1888
|
+
}
|
|
1889
|
+
for (const statement of regularStatements)try {
|
|
1890
|
+
await conn.execute(statement);
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
if (error.message?.includes('already exists') || error.message?.includes('no such column') || error.message?.includes('no such table')) {
|
|
1893
|
+
console.log(` ⚠️ Skipping: ${error.message}`);
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
console.error(`❌ Migration failed: ${statement.substring(0, 100)}...`);
|
|
1897
|
+
throw error;
|
|
1898
|
+
}
|
|
1899
|
+
console.log(`✅ Migrations completed (${regularStatements.length} statements)`);
|
|
1900
|
+
}
|
|
1901
|
+
getDatabaseConfig(name) {
|
|
1902
|
+
switch(this.config.backend){
|
|
1903
|
+
case 'bun-sqlite':
|
|
1904
|
+
return {
|
|
1905
|
+
databasePath: `${this.config.databasePath}/${name}.db`,
|
|
1906
|
+
create: true
|
|
1907
|
+
};
|
|
1908
|
+
case 'libsql':
|
|
1909
|
+
return {
|
|
1910
|
+
url: `file:${this.config.databasePath}/${name}.db`,
|
|
1911
|
+
...this.config.libsql
|
|
1912
|
+
};
|
|
1913
|
+
case 'odblite':
|
|
1914
|
+
return {
|
|
1915
|
+
databaseName: `${this.config.databasePath}_${name}`,
|
|
1916
|
+
...this.config.odblite
|
|
1917
|
+
};
|
|
1918
|
+
default:
|
|
1919
|
+
throw new Error(`Unknown backend: ${this.config.backend}`);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
async evictLRU() {
|
|
1923
|
+
let oldestKey = null;
|
|
1924
|
+
let oldestTime = 1 / 0;
|
|
1925
|
+
for (const [key, timestamp] of this.connectionTimestamps)if (timestamp < oldestTime) {
|
|
1926
|
+
oldestTime = timestamp;
|
|
1927
|
+
oldestKey = key;
|
|
1928
|
+
}
|
|
1929
|
+
if (oldestKey) {
|
|
1930
|
+
const conn = this.connections.get(oldestKey);
|
|
1931
|
+
if (conn) await conn.close();
|
|
1932
|
+
this.connections.delete(oldestKey);
|
|
1933
|
+
this.connectionTimestamps.delete(oldestKey);
|
|
1934
|
+
console.log(`♻️ Evicted LRU connection: ${oldestKey}`);
|
|
1935
|
+
}
|
|
1826
1936
|
}
|
|
1827
1937
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1938
|
+
var orm = __webpack_require__("./src/orm/index.ts");
|
|
1939
|
+
const { raw: src_raw, identifier: src_identifier, where: src_where, insertValues: src_insertValues, updateSet: src_updateSet, join: src_join } = ODBLiteClient;
|
|
1940
|
+
})();
|
|
1941
|
+
exports.BunSQLiteAdapter = __webpack_exports__.BunSQLiteAdapter;
|
|
1942
|
+
exports.ConnectionError = __webpack_exports__.ConnectionError;
|
|
1943
|
+
exports.DatabaseManager = __webpack_exports__.DatabaseManager;
|
|
1944
|
+
exports.HTTPClient = __webpack_exports__.HTTPClient;
|
|
1945
|
+
exports.LibSQLAdapter = __webpack_exports__.LibSQLAdapter;
|
|
1946
|
+
exports.ODBLiteAdapter = __webpack_exports__.ODBLiteAdapter;
|
|
1947
|
+
exports.ODBLiteClient = __webpack_exports__.ODBLiteClient;
|
|
1948
|
+
exports.ODBLiteError = __webpack_exports__.ODBLiteError;
|
|
1949
|
+
exports.ODBLiteTransaction = __webpack_exports__.ODBLiteTransaction;
|
|
1950
|
+
exports.ORM = __webpack_exports__.ORM;
|
|
1951
|
+
exports.QueryError = __webpack_exports__.QueryError;
|
|
1952
|
+
exports.SQLParser = __webpack_exports__.SQLParser;
|
|
1953
|
+
exports.ServiceClient = __webpack_exports__.ServiceClient;
|
|
1954
|
+
exports.SimpleTransaction = __webpack_exports__.SimpleTransaction;
|
|
1955
|
+
exports.and = __webpack_exports__.and;
|
|
1956
|
+
exports.convertTemplateToQuery = __webpack_exports__.convertTemplateToQuery;
|
|
1957
|
+
exports.createORM = __webpack_exports__.createORM;
|
|
1958
|
+
exports["default"] = __webpack_exports__["default"];
|
|
1959
|
+
exports.empty = __webpack_exports__.empty;
|
|
1960
|
+
exports.eq = __webpack_exports__.eq;
|
|
1961
|
+
exports.fragment = __webpack_exports__.fragment;
|
|
1962
|
+
exports.gt = __webpack_exports__.gt;
|
|
1963
|
+
exports.gte = __webpack_exports__.gte;
|
|
1964
|
+
exports.identifier = __webpack_exports__.identifier;
|
|
1965
|
+
exports.inArray = __webpack_exports__.inArray;
|
|
1966
|
+
exports.insertValues = __webpack_exports__.insertValues;
|
|
1967
|
+
exports.isNotNull = __webpack_exports__.isNotNull;
|
|
1968
|
+
exports.isNull = __webpack_exports__.isNull;
|
|
1969
|
+
exports.join = __webpack_exports__.join;
|
|
1970
|
+
exports.like = __webpack_exports__.like;
|
|
1971
|
+
exports.lt = __webpack_exports__.lt;
|
|
1972
|
+
exports.lte = __webpack_exports__.lte;
|
|
1973
|
+
exports.ne = __webpack_exports__.ne;
|
|
1974
|
+
exports.odblite = __webpack_exports__.odblite;
|
|
1975
|
+
exports.or = __webpack_exports__.or;
|
|
1976
|
+
exports.parseSQL = __webpack_exports__.parseSQL;
|
|
1977
|
+
exports.raw = __webpack_exports__.raw;
|
|
1978
|
+
exports.rawSQL = __webpack_exports__.rawSQL;
|
|
1979
|
+
exports.set = __webpack_exports__.set;
|
|
1980
|
+
exports.splitSQLStatements = __webpack_exports__.splitSQLStatements;
|
|
1981
|
+
exports.sql = __webpack_exports__.sql;
|
|
1982
|
+
exports.sqlFragment = __webpack_exports__.sqlFragment;
|
|
1983
|
+
exports.sqlJoin = __webpack_exports__.sqlJoin;
|
|
1984
|
+
exports.sqlTemplate = __webpack_exports__.sqlTemplate;
|
|
1985
|
+
exports.sqlWhere = __webpack_exports__.sqlWhere;
|
|
1986
|
+
exports.updateSet = __webpack_exports__.updateSet;
|
|
1987
|
+
exports.where = __webpack_exports__.where;
|
|
1988
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
1989
|
+
"BunSQLiteAdapter",
|
|
1990
|
+
"ConnectionError",
|
|
1991
|
+
"DatabaseManager",
|
|
1992
|
+
"HTTPClient",
|
|
1993
|
+
"LibSQLAdapter",
|
|
1994
|
+
"ODBLiteAdapter",
|
|
1995
|
+
"ODBLiteClient",
|
|
1996
|
+
"ODBLiteError",
|
|
1997
|
+
"ODBLiteTransaction",
|
|
1998
|
+
"ORM",
|
|
1999
|
+
"QueryError",
|
|
2000
|
+
"SQLParser",
|
|
2001
|
+
"ServiceClient",
|
|
2002
|
+
"SimpleTransaction",
|
|
2003
|
+
"and",
|
|
2004
|
+
"convertTemplateToQuery",
|
|
2005
|
+
"createORM",
|
|
2006
|
+
"default",
|
|
2007
|
+
"empty",
|
|
2008
|
+
"eq",
|
|
2009
|
+
"fragment",
|
|
2010
|
+
"gt",
|
|
2011
|
+
"gte",
|
|
2012
|
+
"identifier",
|
|
2013
|
+
"inArray",
|
|
2014
|
+
"insertValues",
|
|
2015
|
+
"isNotNull",
|
|
2016
|
+
"isNull",
|
|
2017
|
+
"join",
|
|
2018
|
+
"like",
|
|
2019
|
+
"lt",
|
|
2020
|
+
"lte",
|
|
2021
|
+
"ne",
|
|
2022
|
+
"odblite",
|
|
2023
|
+
"or",
|
|
2024
|
+
"parseSQL",
|
|
2025
|
+
"raw",
|
|
2026
|
+
"rawSQL",
|
|
2027
|
+
"set",
|
|
2028
|
+
"splitSQLStatements",
|
|
2029
|
+
"sql",
|
|
2030
|
+
"sqlFragment",
|
|
2031
|
+
"sqlJoin",
|
|
2032
|
+
"sqlTemplate",
|
|
2033
|
+
"sqlWhere",
|
|
2034
|
+
"updateSet",
|
|
2035
|
+
"where"
|
|
2036
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
2037
|
+
Object.defineProperty(exports, '__esModule', {
|
|
1840
2038
|
value: true
|
|
1841
2039
|
});
|