@pineliner/odb-client 1.1.4 → 1.2.0
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/client.d.ts +3 -3
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/http-client.d.ts +8 -1
- package/dist/core/http-client.d.ts.map +1 -1
- package/dist/database/adapters/odblite-socket.d.ts +52 -0
- package/dist/database/adapters/odblite-socket.d.ts.map +1 -0
- package/dist/database/adapters/odblite.d.ts.map +1 -1
- package/dist/database/adapters/socket/client.d.ts +28 -0
- package/dist/database/adapters/socket/client.d.ts.map +1 -0
- package/dist/database/adapters/socket/protocol.d.ts +129 -0
- package/dist/database/adapters/socket/protocol.d.ts.map +1 -0
- package/dist/database/manager.d.ts.map +1 -1
- package/dist/database/sql-parser.d.ts.map +1 -1
- package/dist/database/types.d.ts +6 -1
- package/dist/database/types.d.ts.map +1 -1
- package/dist/index.cjs +513 -30
- package/dist/index.js +515 -30
- package/package.json +2 -2
- package/src/core/client.ts +14 -8
- package/src/core/http-client.ts +22 -7
- package/src/database/adapters/odblite-socket.ts +255 -0
- package/src/database/adapters/odblite.ts +97 -26
- package/src/database/adapters/socket/client.ts +78 -0
- package/src/database/adapters/socket/protocol.ts +147 -0
- package/src/database/manager.ts +19 -1
- package/src/database/sql-parser.ts +23 -3
- package/src/database/types.ts +8 -1
package/dist/index.cjs
CHANGED
|
@@ -412,6 +412,12 @@ var __webpack_modules__ = {
|
|
|
412
412
|
function createORM(db) {
|
|
413
413
|
return new ORM(db);
|
|
414
414
|
}
|
|
415
|
+
},
|
|
416
|
+
async_hooks (module) {
|
|
417
|
+
module.exports = require("async_hooks");
|
|
418
|
+
},
|
|
419
|
+
"node:async_hooks" (module) {
|
|
420
|
+
module.exports = require("node:async_hooks");
|
|
415
421
|
}
|
|
416
422
|
};
|
|
417
423
|
var __webpack_module_cache__ = {};
|
|
@@ -540,12 +546,15 @@ var __webpack_exports__ = {};
|
|
|
540
546
|
};
|
|
541
547
|
if ('string' == typeof this.config.baseUrl) this.config.baseUrl = this.config.baseUrl.replace(/\/$/, '');
|
|
542
548
|
}
|
|
543
|
-
async query(sql, params = []) {
|
|
549
|
+
async query(sql, params = [], opts) {
|
|
544
550
|
if (!this.config.databaseId) throw new ConnectionError('No database ID configured. Use setDatabase() first.');
|
|
545
551
|
const url = `${this.config.baseUrl}/query/${this.config.databaseId}`;
|
|
546
552
|
const body = {
|
|
547
553
|
sql,
|
|
548
|
-
params
|
|
554
|
+
params,
|
|
555
|
+
...opts?.txId ? {
|
|
556
|
+
txId: opts.txId
|
|
557
|
+
} : {}
|
|
549
558
|
};
|
|
550
559
|
try {
|
|
551
560
|
const response = await this.makeRequest(url, {
|
|
@@ -555,7 +564,7 @@ var __webpack_exports__ = {};
|
|
|
555
564
|
Authorization: `Bearer ${this.config.apiKey}`
|
|
556
565
|
},
|
|
557
566
|
body: JSON.stringify(body)
|
|
558
|
-
});
|
|
567
|
+
}, opts?.maxRetries);
|
|
559
568
|
const data = await response.json();
|
|
560
569
|
if (!data.success) throw new types_QueryError(data.error || 'Query failed', sql, params);
|
|
561
570
|
let rows = [];
|
|
@@ -634,9 +643,10 @@ var __webpack_exports__ = {};
|
|
|
634
643
|
setDatabase(databaseId) {
|
|
635
644
|
this.config.databaseId = databaseId;
|
|
636
645
|
}
|
|
637
|
-
async makeRequest(url, options) {
|
|
646
|
+
async makeRequest(url, options, maxRetries) {
|
|
638
647
|
let lastError;
|
|
639
|
-
|
|
648
|
+
const retries = maxRetries ?? this.config.retries ?? 3;
|
|
649
|
+
for(let attempt = 1; attempt <= retries; attempt++)try {
|
|
640
650
|
const controller = new AbortController();
|
|
641
651
|
const timeoutId = setTimeout(()=>controller.abort(), this.config.timeout);
|
|
642
652
|
const response = await fetch(url, {
|
|
@@ -660,12 +670,12 @@ var __webpack_exports__ = {};
|
|
|
660
670
|
} catch (error) {
|
|
661
671
|
lastError = error instanceof Error ? error : new Error('Unknown error');
|
|
662
672
|
if (error instanceof ConnectionError && error.message.includes('HTTP 4')) throw error;
|
|
663
|
-
if (attempt <
|
|
673
|
+
if (attempt < retries) {
|
|
664
674
|
const delay = Math.min(1000 * 2 ** (attempt - 1), 10000);
|
|
665
675
|
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
666
676
|
}
|
|
667
677
|
}
|
|
668
|
-
throw new ConnectionError(`Failed after ${
|
|
678
|
+
throw new ConnectionError(`Failed after ${retries} attempts: ${lastError?.message}`, lastError);
|
|
669
679
|
}
|
|
670
680
|
configure(updates) {
|
|
671
681
|
return new HTTPClient({
|
|
@@ -1138,10 +1148,10 @@ var __webpack_exports__ = {};
|
|
|
1138
1148
|
};
|
|
1139
1149
|
sqlFunction.raw = (text)=>sql_parser_SQLParser.raw(text);
|
|
1140
1150
|
sqlFunction.identifier = (name)=>sql_parser_SQLParser.identifier(name);
|
|
1141
|
-
sqlFunction.query = async (sql, params = [])=>await this.httpClient.query(sql, params);
|
|
1142
|
-
sqlFunction.execute = async (sql, args)=>{
|
|
1143
|
-
if ('string' == typeof sql) return await this.httpClient.query(sql, args || []);
|
|
1144
|
-
return await this.httpClient.query(sql.sql, sql.args || []);
|
|
1151
|
+
sqlFunction.query = async (sql, params = [], opts)=>await this.httpClient.query(sql, params, opts);
|
|
1152
|
+
sqlFunction.execute = async (sql, args, opts)=>{
|
|
1153
|
+
if ('string' == typeof sql) return await this.httpClient.query(sql, args || [], opts);
|
|
1154
|
+
return await this.httpClient.query(sql.sql, sql.args || [], opts);
|
|
1145
1155
|
};
|
|
1146
1156
|
sqlFunction.batch = async (statements)=>await this.httpClient.batch(statements);
|
|
1147
1157
|
sqlFunction.begin = async (modeOrCallback, callback)=>{
|
|
@@ -1885,6 +1895,25 @@ var __webpack_exports__ = {};
|
|
|
1885
1895
|
return createORM(this);
|
|
1886
1896
|
}
|
|
1887
1897
|
}
|
|
1898
|
+
let AsyncLocalStorageCtor = null;
|
|
1899
|
+
try {
|
|
1900
|
+
AsyncLocalStorageCtor = __webpack_require__("node:async_hooks").AsyncLocalStorage;
|
|
1901
|
+
} catch {
|
|
1902
|
+
try {
|
|
1903
|
+
AsyncLocalStorageCtor = __webpack_require__("async_hooks").AsyncLocalStorage;
|
|
1904
|
+
} catch {
|
|
1905
|
+
AsyncLocalStorageCtor = null;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
let txCounter = 0;
|
|
1909
|
+
function newTxId() {
|
|
1910
|
+
try {
|
|
1911
|
+
const c = globalThis.crypto;
|
|
1912
|
+
if (c?.randomUUID) return `tx_${c.randomUUID()}`;
|
|
1913
|
+
} catch {}
|
|
1914
|
+
txCounter = (txCounter + 1) % Number.MAX_SAFE_INTEGER;
|
|
1915
|
+
return `tx_${Date.now().toString(36)}_${txCounter.toString(36)}`;
|
|
1916
|
+
}
|
|
1888
1917
|
function odblite_parseJsonColumns(rows, jsonColumns) {
|
|
1889
1918
|
if (!jsonColumns || 0 === jsonColumns.length) return rows;
|
|
1890
1919
|
return rows.map((row)=>{
|
|
@@ -1959,7 +1988,8 @@ var __webpack_exports__ = {};
|
|
|
1959
1988
|
class ODBLiteConnection {
|
|
1960
1989
|
client;
|
|
1961
1990
|
serviceClient;
|
|
1962
|
-
|
|
1991
|
+
txStorage = AsyncLocalStorageCtor ? new AsyncLocalStorageCtor() : null;
|
|
1992
|
+
fallbackTxId;
|
|
1963
1993
|
databaseName;
|
|
1964
1994
|
databaseHash;
|
|
1965
1995
|
sql;
|
|
@@ -1994,11 +2024,22 @@ var __webpack_exports__ = {};
|
|
|
1994
2024
|
async query(sql, params = [], options) {
|
|
1995
2025
|
return this.execute(sql, params, options);
|
|
1996
2026
|
}
|
|
2027
|
+
activeTxId() {
|
|
2028
|
+
return this.txStorage ? this.txStorage.getStore()?.txId : this.fallbackTxId;
|
|
2029
|
+
}
|
|
2030
|
+
requestOpts() {
|
|
2031
|
+
const txId = this.activeTxId();
|
|
2032
|
+
return txId ? {
|
|
2033
|
+
txId,
|
|
2034
|
+
maxRetries: 1
|
|
2035
|
+
} : void 0;
|
|
2036
|
+
}
|
|
1997
2037
|
async execute(sql, params = [], options) {
|
|
1998
2038
|
try {
|
|
1999
2039
|
let rows;
|
|
2000
2040
|
let rowsAffected;
|
|
2001
2041
|
let lastInsertRowid;
|
|
2042
|
+
const reqOpts = this.requestOpts();
|
|
2002
2043
|
if ('object' == typeof sql) {
|
|
2003
2044
|
if (process.env.DEBUG_SQL) {
|
|
2004
2045
|
console.log('[ODBLite] Executing SQL:', sql.sql);
|
|
@@ -2006,7 +2047,7 @@ var __webpack_exports__ = {};
|
|
|
2006
2047
|
}
|
|
2007
2048
|
let args = sql.args || [];
|
|
2008
2049
|
if (options?.stringifyParams) args = odblite_stringifyJsonParams(args, options.stringifyParams);
|
|
2009
|
-
const result = await this.client.sql.execute(sql.sql, args);
|
|
2050
|
+
const result = await this.client.sql.execute(sql.sql, args, reqOpts);
|
|
2010
2051
|
rows = result.rows;
|
|
2011
2052
|
rowsAffected = result.rowsAffected || 0;
|
|
2012
2053
|
lastInsertRowid = result.lastInsertRowid;
|
|
@@ -2017,7 +2058,7 @@ var __webpack_exports__ = {};
|
|
|
2017
2058
|
}
|
|
2018
2059
|
let args = params;
|
|
2019
2060
|
if (options?.stringifyParams) args = odblite_stringifyJsonParams(args, options.stringifyParams);
|
|
2020
|
-
const result = await this.client.sql.execute(sql, args);
|
|
2061
|
+
const result = await this.client.sql.execute(sql, args, reqOpts);
|
|
2021
2062
|
rows = result.rows;
|
|
2022
2063
|
rowsAffected = result.rowsAffected || 0;
|
|
2023
2064
|
lastInsertRowid = result.lastInsertRowid;
|
|
@@ -2046,27 +2087,34 @@ var __webpack_exports__ = {};
|
|
|
2046
2087
|
};
|
|
2047
2088
|
}
|
|
2048
2089
|
async transaction(fn) {
|
|
2049
|
-
if (this.
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2090
|
+
if (this.activeTxId()) return fn(this);
|
|
2091
|
+
const txId = newTxId();
|
|
2092
|
+
const run = async ()=>{
|
|
2093
|
+
const prevFallback = this.fallbackTxId;
|
|
2094
|
+
if (!this.txStorage) this.fallbackTxId = txId;
|
|
2053
2095
|
try {
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2096
|
+
await this.execute('BEGIN');
|
|
2097
|
+
try {
|
|
2098
|
+
const result = await fn(this);
|
|
2099
|
+
await this.execute('COMMIT');
|
|
2100
|
+
return result;
|
|
2101
|
+
} catch (error) {
|
|
2102
|
+
await this.execute('ROLLBACK').catch(()=>{});
|
|
2103
|
+
throw error;
|
|
2104
|
+
}
|
|
2105
|
+
} finally{
|
|
2106
|
+
if (!this.txStorage) this.fallbackTxId = prevFallback;
|
|
2060
2107
|
}
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2108
|
+
};
|
|
2109
|
+
return this.txStorage ? this.txStorage.run({
|
|
2110
|
+
txId
|
|
2111
|
+
}, run) : run();
|
|
2064
2112
|
}
|
|
2065
2113
|
async begin(fn) {
|
|
2066
2114
|
return this.transaction(fn);
|
|
2067
2115
|
}
|
|
2068
2116
|
async savepoint(fn) {
|
|
2069
|
-
if (!this.
|
|
2117
|
+
if (!this.activeTxId()) return this.transaction(fn);
|
|
2070
2118
|
const name = `sp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2071
2119
|
try {
|
|
2072
2120
|
await this.execute(`SAVEPOINT ${name}`);
|
|
@@ -2090,6 +2138,417 @@ var __webpack_exports__ = {};
|
|
|
2090
2138
|
return this.client.httpClient.getNamespaces();
|
|
2091
2139
|
}
|
|
2092
2140
|
}
|
|
2141
|
+
function toHrana(v) {
|
|
2142
|
+
if (null == v) return {
|
|
2143
|
+
type: 'null'
|
|
2144
|
+
};
|
|
2145
|
+
if ('bigint' == typeof v) return {
|
|
2146
|
+
type: 'integer',
|
|
2147
|
+
value: v.toString()
|
|
2148
|
+
};
|
|
2149
|
+
if ('number' == typeof v) return Number.isInteger(v) ? {
|
|
2150
|
+
type: 'integer',
|
|
2151
|
+
value: String(v)
|
|
2152
|
+
} : {
|
|
2153
|
+
type: 'float',
|
|
2154
|
+
value: v
|
|
2155
|
+
};
|
|
2156
|
+
if ('string' == typeof v) return {
|
|
2157
|
+
type: 'text',
|
|
2158
|
+
value: v
|
|
2159
|
+
};
|
|
2160
|
+
if (v instanceof Uint8Array) return {
|
|
2161
|
+
type: 'blob',
|
|
2162
|
+
base64: Buffer.from(v).toString('base64')
|
|
2163
|
+
};
|
|
2164
|
+
if ('boolean' == typeof v) return {
|
|
2165
|
+
type: 'integer',
|
|
2166
|
+
value: v ? '1' : '0'
|
|
2167
|
+
};
|
|
2168
|
+
return {
|
|
2169
|
+
type: 'text',
|
|
2170
|
+
value: String(v)
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
function fromHrana(v) {
|
|
2174
|
+
switch(v.type){
|
|
2175
|
+
case 'null':
|
|
2176
|
+
return null;
|
|
2177
|
+
case 'integer':
|
|
2178
|
+
{
|
|
2179
|
+
const n = Number(v.value);
|
|
2180
|
+
return Number.isSafeInteger(n) ? n : BigInt(v.value);
|
|
2181
|
+
}
|
|
2182
|
+
case 'float':
|
|
2183
|
+
return v.value;
|
|
2184
|
+
case 'text':
|
|
2185
|
+
return v.value;
|
|
2186
|
+
case 'blob':
|
|
2187
|
+
return new Uint8Array(Buffer.from(v.base64, 'base64'));
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function toBatch(steps) {
|
|
2191
|
+
return {
|
|
2192
|
+
steps: steps.map((s, i)=>({
|
|
2193
|
+
condition: 0 === i ? void 0 : {
|
|
2194
|
+
type: 'ok',
|
|
2195
|
+
step: i - 1
|
|
2196
|
+
},
|
|
2197
|
+
stmt: {
|
|
2198
|
+
sql: s.sql,
|
|
2199
|
+
args: (s.params ?? []).map(toHrana)
|
|
2200
|
+
},
|
|
2201
|
+
expect: s.expect ? {
|
|
2202
|
+
affected: s.expect
|
|
2203
|
+
} : void 0
|
|
2204
|
+
}))
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
class FrameWriter {
|
|
2208
|
+
socket;
|
|
2209
|
+
pending = Buffer.alloc(0);
|
|
2210
|
+
constructor(socket){
|
|
2211
|
+
this.socket = socket;
|
|
2212
|
+
}
|
|
2213
|
+
send(payload) {
|
|
2214
|
+
const header = Buffer.allocUnsafe(4);
|
|
2215
|
+
header.writeUInt32LE(payload.length, 0);
|
|
2216
|
+
const frame = Buffer.concat([
|
|
2217
|
+
header,
|
|
2218
|
+
payload
|
|
2219
|
+
]);
|
|
2220
|
+
this.pending = this.pending.length ? Buffer.concat([
|
|
2221
|
+
this.pending,
|
|
2222
|
+
frame
|
|
2223
|
+
]) : frame;
|
|
2224
|
+
this.flush();
|
|
2225
|
+
}
|
|
2226
|
+
flush() {
|
|
2227
|
+
while(this.pending.length > 0){
|
|
2228
|
+
const n = this.socket.write(this.pending);
|
|
2229
|
+
if (n <= 0) return;
|
|
2230
|
+
this.pending = n < this.pending.length ? this.pending.subarray(n) : Buffer.alloc(0);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
class FrameReader {
|
|
2235
|
+
buf = Buffer.alloc(0);
|
|
2236
|
+
push(chunk) {
|
|
2237
|
+
this.buf = this.buf.length ? Buffer.concat([
|
|
2238
|
+
this.buf,
|
|
2239
|
+
chunk
|
|
2240
|
+
]) : chunk;
|
|
2241
|
+
const out = [];
|
|
2242
|
+
while(this.buf.length >= 4){
|
|
2243
|
+
const len = this.buf.readUInt32LE(0);
|
|
2244
|
+
if (this.buf.length < 4 + len) break;
|
|
2245
|
+
out.push(this.buf.subarray(4, 4 + len));
|
|
2246
|
+
this.buf = this.buf.subarray(4 + len);
|
|
2247
|
+
}
|
|
2248
|
+
return out;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
class SocketClient {
|
|
2252
|
+
target;
|
|
2253
|
+
socket;
|
|
2254
|
+
reader = new FrameReader();
|
|
2255
|
+
writer;
|
|
2256
|
+
pending = new Map();
|
|
2257
|
+
nextId = 1;
|
|
2258
|
+
ready;
|
|
2259
|
+
constructor(target){
|
|
2260
|
+
this.target = target;
|
|
2261
|
+
this.ready = this.connect();
|
|
2262
|
+
this.ready.catch(()=>{});
|
|
2263
|
+
}
|
|
2264
|
+
async connect() {
|
|
2265
|
+
const Bun = globalThis.Bun;
|
|
2266
|
+
if (!Bun?.connect) throw new Error('odblite-socket backend requires Bun (Bun.connect)');
|
|
2267
|
+
const opts = this.target.unix ? {
|
|
2268
|
+
unix: this.target.unix
|
|
2269
|
+
} : {
|
|
2270
|
+
hostname: this.target.hostname ?? '127.0.0.1',
|
|
2271
|
+
port: this.target.port ?? 8787
|
|
2272
|
+
};
|
|
2273
|
+
this.socket = await Bun.connect({
|
|
2274
|
+
...opts,
|
|
2275
|
+
socket: {
|
|
2276
|
+
data: (_s, chunk)=>{
|
|
2277
|
+
for (const frame of this.reader.push(chunk)){
|
|
2278
|
+
const msg = JSON.parse(frame.toString('utf8'));
|
|
2279
|
+
const resolve = this.pending.get(msg.id);
|
|
2280
|
+
if (resolve) {
|
|
2281
|
+
this.pending.delete(msg.id);
|
|
2282
|
+
resolve(msg.results);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
},
|
|
2286
|
+
drain: (_s)=>this.writer.flush(),
|
|
2287
|
+
close: ()=>{
|
|
2288
|
+
this.socket = void 0;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
this.writer = new FrameWriter(this.socket);
|
|
2293
|
+
}
|
|
2294
|
+
async pipeline(tenant, requests) {
|
|
2295
|
+
if (!this.socket) {
|
|
2296
|
+
this.ready = this.connect();
|
|
2297
|
+
this.ready.catch(()=>{});
|
|
2298
|
+
}
|
|
2299
|
+
await this.ready;
|
|
2300
|
+
const id = this.nextId++;
|
|
2301
|
+
return new Promise((resolve)=>{
|
|
2302
|
+
this.pending.set(id, resolve);
|
|
2303
|
+
this.writer.send(Buffer.from(JSON.stringify({
|
|
2304
|
+
id,
|
|
2305
|
+
tenant,
|
|
2306
|
+
requests
|
|
2307
|
+
}), 'utf8'));
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
close() {
|
|
2311
|
+
this.socket?.end?.();
|
|
2312
|
+
this.socket = void 0;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
function toQueryResult(res) {
|
|
2316
|
+
if ('error' === res.type) throw new Error(res.error.message);
|
|
2317
|
+
const r = res.response.result;
|
|
2318
|
+
const rows = r.rows.map((row)=>{
|
|
2319
|
+
const obj = {};
|
|
2320
|
+
r.cols.forEach((c, i)=>{
|
|
2321
|
+
obj[c.name ?? `c${i}`] = fromHrana(row[i] ?? {
|
|
2322
|
+
type: 'null'
|
|
2323
|
+
});
|
|
2324
|
+
});
|
|
2325
|
+
return obj;
|
|
2326
|
+
});
|
|
2327
|
+
return {
|
|
2328
|
+
rows,
|
|
2329
|
+
rowsAffected: r.affected_row_count,
|
|
2330
|
+
lastInsertRowid: r.last_insert_rowid ? Number(r.last_insert_rowid) : void 0
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
class OdbliteSocketConnection {
|
|
2334
|
+
client;
|
|
2335
|
+
tenant;
|
|
2336
|
+
databaseName;
|
|
2337
|
+
databaseHash;
|
|
2338
|
+
sql;
|
|
2339
|
+
buffer;
|
|
2340
|
+
constructor(client, tenant, buffer){
|
|
2341
|
+
this.client = client;
|
|
2342
|
+
this.tenant = tenant;
|
|
2343
|
+
this.databaseName = tenant;
|
|
2344
|
+
this.databaseHash = tenant;
|
|
2345
|
+
this.buffer = buffer;
|
|
2346
|
+
const self = this;
|
|
2347
|
+
this.sql = function(strings, ...values) {
|
|
2348
|
+
if (strings && 'object' == typeof strings && 'raw' in strings) {
|
|
2349
|
+
const text = strings.reduce((a, s, i)=>a + s + (i < values.length ? '?' : ''), '');
|
|
2350
|
+
const returns = /^\s*SELECT/i.test(text) || /\bRETURNING\b/i.test(text);
|
|
2351
|
+
return (returns ? self.query(text, values) : self.execute(text, values)).then((r)=>r.rows);
|
|
2352
|
+
}
|
|
2353
|
+
throw new Error('sql() fragment helper not implemented in odblite-socket');
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
async one(requests) {
|
|
2357
|
+
const [res] = await this.client.pipeline(this.tenant, requests);
|
|
2358
|
+
if (!res) throw new Error('odblite-socket: empty pipeline response');
|
|
2359
|
+
return res;
|
|
2360
|
+
}
|
|
2361
|
+
async query(sql, params = []) {
|
|
2362
|
+
return toQueryResult(await this.one([
|
|
2363
|
+
{
|
|
2364
|
+
type: 'execute',
|
|
2365
|
+
stmt: {
|
|
2366
|
+
sql,
|
|
2367
|
+
args: params.map(toHrana)
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
]));
|
|
2371
|
+
}
|
|
2372
|
+
async execute(sql, params = []) {
|
|
2373
|
+
const s = 'string' == typeof sql ? sql : sql.sql;
|
|
2374
|
+
const p = 'string' == typeof sql ? params : sql.args ?? [];
|
|
2375
|
+
if (this.buffer) {
|
|
2376
|
+
this.buffer.push({
|
|
2377
|
+
sql: s,
|
|
2378
|
+
params: p
|
|
2379
|
+
});
|
|
2380
|
+
return {
|
|
2381
|
+
rows: [],
|
|
2382
|
+
rowsAffected: 0
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
return toQueryResult(await this.one([
|
|
2386
|
+
{
|
|
2387
|
+
type: 'execute',
|
|
2388
|
+
stmt: {
|
|
2389
|
+
sql: s,
|
|
2390
|
+
args: p.map(toHrana)
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
]));
|
|
2394
|
+
}
|
|
2395
|
+
async atomic(steps) {
|
|
2396
|
+
const res = await this.one([
|
|
2397
|
+
{
|
|
2398
|
+
type: 'batch',
|
|
2399
|
+
batch: toBatch(steps)
|
|
2400
|
+
}
|
|
2401
|
+
]);
|
|
2402
|
+
if ('error' === res.type) throw new Error(res.error.message);
|
|
2403
|
+
return res.response.result;
|
|
2404
|
+
}
|
|
2405
|
+
prepare(sql) {
|
|
2406
|
+
return {
|
|
2407
|
+
execute: async (params = [])=>this.execute(sql, params),
|
|
2408
|
+
all: async (params = [])=>(await this.query(sql, params)).rows,
|
|
2409
|
+
get: async (params = [])=>(await this.query(sql, params)).rows[0] ?? null
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
async transaction(fn) {
|
|
2413
|
+
if (this.buffer) return fn(this);
|
|
2414
|
+
const buffer = [];
|
|
2415
|
+
const out = await fn(new OdbliteSocketConnection(this.client, this.tenant, buffer));
|
|
2416
|
+
if (buffer.length) {
|
|
2417
|
+
const res = await this.atomic(buffer.map((b)=>({
|
|
2418
|
+
sql: b.sql,
|
|
2419
|
+
params: b.params
|
|
2420
|
+
})));
|
|
2421
|
+
const err = res.step_errors?.find((e)=>e);
|
|
2422
|
+
if (err) throw new Error(`transaction batch failed: ${err.message}`);
|
|
2423
|
+
}
|
|
2424
|
+
return out;
|
|
2425
|
+
}
|
|
2426
|
+
begin(fn) {
|
|
2427
|
+
return this.transaction(fn);
|
|
2428
|
+
}
|
|
2429
|
+
savepoint(fn) {
|
|
2430
|
+
return this.transaction(fn);
|
|
2431
|
+
}
|
|
2432
|
+
async ensureNamespaces(namespaces) {
|
|
2433
|
+
return {
|
|
2434
|
+
success: true,
|
|
2435
|
+
databaseName: this.tenant,
|
|
2436
|
+
namespaces
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
async getNamespaces() {
|
|
2440
|
+
const ns = (await this.query("SELECT name FROM pragma_database_list WHERE name != 'main'")).rows.map((r)=>r.name);
|
|
2441
|
+
return {
|
|
2442
|
+
success: true,
|
|
2443
|
+
databaseName: this.tenant,
|
|
2444
|
+
exists: true,
|
|
2445
|
+
namespaces: ns,
|
|
2446
|
+
registered: true,
|
|
2447
|
+
registeredNamespaces: ns
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
async close() {}
|
|
2451
|
+
}
|
|
2452
|
+
class OdbliteSocketAdapter {
|
|
2453
|
+
type = 'odblite-socket';
|
|
2454
|
+
client;
|
|
2455
|
+
constructor(config){
|
|
2456
|
+
this.client = new SocketClient(config);
|
|
2457
|
+
}
|
|
2458
|
+
async connect(config) {
|
|
2459
|
+
const tenant = config?.databaseHash ?? config?.databaseName;
|
|
2460
|
+
if (!tenant) throw new Error('odblite-socket: databaseHash (or databaseName) required');
|
|
2461
|
+
return new OdbliteSocketConnection(this.client, String(tenant));
|
|
2462
|
+
}
|
|
2463
|
+
async disconnect() {
|
|
2464
|
+
this.client.close();
|
|
2465
|
+
}
|
|
2466
|
+
async isHealthy() {
|
|
2467
|
+
try {
|
|
2468
|
+
await Promise.race([
|
|
2469
|
+
this.client.pipeline('_health', [
|
|
2470
|
+
{
|
|
2471
|
+
type: 'get_autocommit'
|
|
2472
|
+
}
|
|
2473
|
+
]),
|
|
2474
|
+
new Promise((_, rej)=>setTimeout(()=>rej(new Error('probe timeout')), 1500))
|
|
2475
|
+
]);
|
|
2476
|
+
return true;
|
|
2477
|
+
} catch {
|
|
2478
|
+
return false;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
const DEFAULT_UNIX_SOCKET = '/tmp/odblite.sock';
|
|
2483
|
+
function parseSocketTarget(s) {
|
|
2484
|
+
const v = (s ?? '').trim();
|
|
2485
|
+
if (!v) return {
|
|
2486
|
+
unix: DEFAULT_UNIX_SOCKET
|
|
2487
|
+
};
|
|
2488
|
+
if (v.includes('/')) return {
|
|
2489
|
+
unix: v
|
|
2490
|
+
};
|
|
2491
|
+
const m = v.match(/^(?:([^:]+):)?(\d+)$/);
|
|
2492
|
+
if (m) return {
|
|
2493
|
+
hostname: m[1] || '127.0.0.1',
|
|
2494
|
+
port: Number(m[2])
|
|
2495
|
+
};
|
|
2496
|
+
return {
|
|
2497
|
+
unix: v
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
class OdbliteAutoAdapter {
|
|
2501
|
+
type = 'odblite';
|
|
2502
|
+
chosen;
|
|
2503
|
+
constructor(opts){
|
|
2504
|
+
this.chosen = this.pick(opts);
|
|
2505
|
+
}
|
|
2506
|
+
async pick(opts) {
|
|
2507
|
+
const hasBun = void 0 !== globalThis.Bun && !!globalThis.Bun.connect;
|
|
2508
|
+
if (hasBun) for (const target of this.candidates(opts))try {
|
|
2509
|
+
const socket = new OdbliteSocketAdapter(target);
|
|
2510
|
+
if (await socket.isHealthy()) {
|
|
2511
|
+
console.log(`[odb-client] transport → SOCKET (router_v2 @ ${target.unix ?? `${target.hostname}:${target.port}`})`);
|
|
2512
|
+
return socket;
|
|
2513
|
+
}
|
|
2514
|
+
await socket.disconnect();
|
|
2515
|
+
} catch {}
|
|
2516
|
+
console.log('[odb-client] transport → HTTP (no router_v2 socket reachable)');
|
|
2517
|
+
return new ODBLiteAdapter(opts.http);
|
|
2518
|
+
}
|
|
2519
|
+
candidates(opts) {
|
|
2520
|
+
if (opts.socket?.unix || opts.socket?.port) return [
|
|
2521
|
+
opts.socket
|
|
2522
|
+
];
|
|
2523
|
+
const env = globalThis.process?.env?.ODB_SOCKET;
|
|
2524
|
+
if (env) return [
|
|
2525
|
+
parseSocketTarget(env)
|
|
2526
|
+
];
|
|
2527
|
+
const out = [
|
|
2528
|
+
{
|
|
2529
|
+
unix: DEFAULT_UNIX_SOCKET
|
|
2530
|
+
}
|
|
2531
|
+
];
|
|
2532
|
+
try {
|
|
2533
|
+
const host = new URL(opts.http?.serviceUrl).hostname;
|
|
2534
|
+
const port = Number(globalThis.process?.env?.ODB_SOCKET_PORT ?? 8787);
|
|
2535
|
+
if (host) out.push({
|
|
2536
|
+
hostname: host,
|
|
2537
|
+
port
|
|
2538
|
+
});
|
|
2539
|
+
} catch {}
|
|
2540
|
+
return out;
|
|
2541
|
+
}
|
|
2542
|
+
async connect(config) {
|
|
2543
|
+
return (await this.chosen).connect(config);
|
|
2544
|
+
}
|
|
2545
|
+
async disconnect(tenantId) {
|
|
2546
|
+
return (await this.chosen).disconnect(tenantId);
|
|
2547
|
+
}
|
|
2548
|
+
async isHealthy() {
|
|
2549
|
+
return (await this.chosen).isHealthy();
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2093
2552
|
function parseSQL(sqlContent, options = {}) {
|
|
2094
2553
|
const separatePragma = options.separatePragma ?? true;
|
|
2095
2554
|
const statements = splitStatements(sqlContent);
|
|
@@ -2115,6 +2574,8 @@ var __webpack_exports__ = {};
|
|
|
2115
2574
|
let inQuote = false;
|
|
2116
2575
|
let quoteChar = '';
|
|
2117
2576
|
let beginEndDepth = 0;
|
|
2577
|
+
let inCte = false;
|
|
2578
|
+
let cteParenDepth = 0;
|
|
2118
2579
|
const lines = sqlContent.split('\n');
|
|
2119
2580
|
for (const line of lines){
|
|
2120
2581
|
let processedLine = '';
|
|
@@ -2128,6 +2589,15 @@ var __webpack_exports__ = {};
|
|
|
2128
2589
|
if (beginEndDepth < 0) beginEndDepth = 0;
|
|
2129
2590
|
}
|
|
2130
2591
|
}
|
|
2592
|
+
if (!inQuote && lineWithoutComments.trim()) {
|
|
2593
|
+
const withMatch = lineWithoutComments.match(/\bWITH\b.*\bAS\b/i);
|
|
2594
|
+
if (withMatch) inCte = true;
|
|
2595
|
+
if (inCte) for (const char of lineWithoutComments){
|
|
2596
|
+
if ('(' === char) cteParenDepth++;
|
|
2597
|
+
if (')' === char) cteParenDepth--;
|
|
2598
|
+
if (0 === cteParenDepth && ')' === char) inCte = false;
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2131
2601
|
for(let i = 0; i < line.length; i++){
|
|
2132
2602
|
const char = line[i];
|
|
2133
2603
|
const prevChar = i > 0 ? line[i - 1] : '';
|
|
@@ -2143,7 +2613,7 @@ var __webpack_exports__ = {};
|
|
|
2143
2613
|
quoteChar = char;
|
|
2144
2614
|
}
|
|
2145
2615
|
processedLine += char;
|
|
2146
|
-
if (';' === char && !inQuote && 0 === beginEndDepth) {
|
|
2616
|
+
if (';' === char && !inQuote && 0 === beginEndDepth && !inCte) {
|
|
2147
2617
|
currentStatement += processedLine;
|
|
2148
2618
|
const stmt = currentStatement.trim();
|
|
2149
2619
|
if (stmt && !stmt.startsWith('--')) statements.push(stmt);
|
|
@@ -2190,7 +2660,14 @@ var __webpack_exports__ = {};
|
|
|
2190
2660
|
break;
|
|
2191
2661
|
case 'odblite':
|
|
2192
2662
|
if (!config.odblite) throw new Error('odblite config required for odblite backend');
|
|
2193
|
-
this.adapter = new
|
|
2663
|
+
this.adapter = new OdbliteAutoAdapter({
|
|
2664
|
+
socket: config.odbliteSocket,
|
|
2665
|
+
http: config.odblite
|
|
2666
|
+
});
|
|
2667
|
+
break;
|
|
2668
|
+
case 'odblite-socket':
|
|
2669
|
+
if (!config.odbliteSocket) throw new Error('odbliteSocket config required for odblite-socket backend');
|
|
2670
|
+
this.adapter = new OdbliteSocketAdapter(config.odbliteSocket);
|
|
2194
2671
|
break;
|
|
2195
2672
|
default:
|
|
2196
2673
|
throw new Error(`Unknown backend type: ${config.backend}`);
|
|
@@ -2308,6 +2785,12 @@ var __webpack_exports__ = {};
|
|
|
2308
2785
|
databaseName: `${this.config.databasePath}_${name}`,
|
|
2309
2786
|
...this.config.odblite
|
|
2310
2787
|
};
|
|
2788
|
+
case 'odblite-socket':
|
|
2789
|
+
return {
|
|
2790
|
+
databaseName: name,
|
|
2791
|
+
databaseHash: name,
|
|
2792
|
+
...this.config.odbliteSocket
|
|
2793
|
+
};
|
|
2311
2794
|
default:
|
|
2312
2795
|
throw new Error(`Unknown backend: ${this.config.backend}`);
|
|
2313
2796
|
}
|