@pineliner/odb-client 1.1.5 → 1.4.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/database/adapters/odblite-socket.d.ts +65 -0
- package/dist/database/adapters/odblite-socket.d.ts.map +1 -0
- package/dist/database/adapters/socket/client.d.ts +41 -0
- package/dist/database/adapters/socket/client.d.ts.map +1 -0
- package/dist/database/adapters/socket/protocol.d.ts +132 -0
- package/dist/database/adapters/socket/protocol.d.ts.map +1 -0
- package/dist/database/manager.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 +522 -1
- package/dist/index.js +522 -1
- package/package.json +1 -1
- package/src/database/adapters/odblite-socket.ts +335 -0
- package/src/database/adapters/socket/client.ts +134 -0
- package/src/database/adapters/socket/protocol.ts +157 -0
- package/src/database/manager.ts +19 -1
- package/src/database/types.ts +8 -1
package/dist/index.js
CHANGED
|
@@ -2048,6 +2048,514 @@ class ODBLiteConnection {
|
|
|
2048
2048
|
return this.client.httpClient.getNamespaces();
|
|
2049
2049
|
}
|
|
2050
2050
|
}
|
|
2051
|
+
function toHrana(v) {
|
|
2052
|
+
if (null == v) return {
|
|
2053
|
+
type: 'null'
|
|
2054
|
+
};
|
|
2055
|
+
if ('bigint' == typeof v) return {
|
|
2056
|
+
type: 'integer',
|
|
2057
|
+
value: v.toString()
|
|
2058
|
+
};
|
|
2059
|
+
if ('number' == typeof v) return Number.isInteger(v) ? {
|
|
2060
|
+
type: 'integer',
|
|
2061
|
+
value: String(v)
|
|
2062
|
+
} : {
|
|
2063
|
+
type: 'float',
|
|
2064
|
+
value: v
|
|
2065
|
+
};
|
|
2066
|
+
if ('string' == typeof v) return {
|
|
2067
|
+
type: 'text',
|
|
2068
|
+
value: v
|
|
2069
|
+
};
|
|
2070
|
+
if (v instanceof Uint8Array) return {
|
|
2071
|
+
type: 'blob',
|
|
2072
|
+
base64: Buffer.from(v).toString('base64')
|
|
2073
|
+
};
|
|
2074
|
+
if ('boolean' == typeof v) return {
|
|
2075
|
+
type: 'integer',
|
|
2076
|
+
value: v ? '1' : '0'
|
|
2077
|
+
};
|
|
2078
|
+
return {
|
|
2079
|
+
type: 'text',
|
|
2080
|
+
value: String(v)
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
function fromHrana(v) {
|
|
2084
|
+
switch(v.type){
|
|
2085
|
+
case 'null':
|
|
2086
|
+
return null;
|
|
2087
|
+
case 'integer':
|
|
2088
|
+
{
|
|
2089
|
+
const n = Number(v.value);
|
|
2090
|
+
return Number.isSafeInteger(n) ? n : BigInt(v.value);
|
|
2091
|
+
}
|
|
2092
|
+
case 'float':
|
|
2093
|
+
return v.value;
|
|
2094
|
+
case 'text':
|
|
2095
|
+
return v.value;
|
|
2096
|
+
case 'blob':
|
|
2097
|
+
return new Uint8Array(Buffer.from(v.base64, 'base64'));
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
function toBatch(steps) {
|
|
2101
|
+
return {
|
|
2102
|
+
steps: steps.map((s, i)=>({
|
|
2103
|
+
condition: 0 === i ? void 0 : {
|
|
2104
|
+
type: 'ok',
|
|
2105
|
+
step: i - 1
|
|
2106
|
+
},
|
|
2107
|
+
stmt: {
|
|
2108
|
+
sql: s.sql,
|
|
2109
|
+
args: (s.params ?? []).map(toHrana)
|
|
2110
|
+
},
|
|
2111
|
+
expect: s.expect ? {
|
|
2112
|
+
affected: s.expect
|
|
2113
|
+
} : void 0
|
|
2114
|
+
}))
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
const READ_ONLY_RE = /^\s*(?:SELECT|EXPLAIN|VALUES)\b/i;
|
|
2118
|
+
function mightReturnRows(sql) {
|
|
2119
|
+
return READ_ONLY_RE.test(sql) || /\bRETURNING\b/i.test(sql);
|
|
2120
|
+
}
|
|
2121
|
+
class FrameWriter {
|
|
2122
|
+
socket;
|
|
2123
|
+
pending = Buffer.alloc(0);
|
|
2124
|
+
constructor(socket){
|
|
2125
|
+
this.socket = socket;
|
|
2126
|
+
}
|
|
2127
|
+
send(payload) {
|
|
2128
|
+
const header = Buffer.allocUnsafe(4);
|
|
2129
|
+
header.writeUInt32LE(payload.length, 0);
|
|
2130
|
+
const frame = Buffer.concat([
|
|
2131
|
+
header,
|
|
2132
|
+
payload
|
|
2133
|
+
]);
|
|
2134
|
+
this.pending = this.pending.length ? Buffer.concat([
|
|
2135
|
+
this.pending,
|
|
2136
|
+
frame
|
|
2137
|
+
]) : frame;
|
|
2138
|
+
this.flush();
|
|
2139
|
+
}
|
|
2140
|
+
flush() {
|
|
2141
|
+
while(this.pending.length > 0){
|
|
2142
|
+
const n = this.socket.write(this.pending);
|
|
2143
|
+
if (n <= 0) return;
|
|
2144
|
+
this.pending = n < this.pending.length ? this.pending.subarray(n) : Buffer.alloc(0);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
class FrameReader {
|
|
2149
|
+
buf = Buffer.alloc(0);
|
|
2150
|
+
push(chunk) {
|
|
2151
|
+
this.buf = this.buf.length ? Buffer.concat([
|
|
2152
|
+
this.buf,
|
|
2153
|
+
chunk
|
|
2154
|
+
]) : chunk;
|
|
2155
|
+
const out = [];
|
|
2156
|
+
while(this.buf.length >= 4){
|
|
2157
|
+
const len = this.buf.readUInt32LE(0);
|
|
2158
|
+
if (this.buf.length < 4 + len) break;
|
|
2159
|
+
out.push(this.buf.subarray(4, 4 + len));
|
|
2160
|
+
this.buf = this.buf.subarray(4 + len);
|
|
2161
|
+
}
|
|
2162
|
+
return out;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
const REQUEST_TIMEOUT_MS = Number(globalThis.process?.env?.ODB_SOCKET_REQUEST_TIMEOUT_MS) || 30000;
|
|
2166
|
+
class SocketClient {
|
|
2167
|
+
target;
|
|
2168
|
+
socket;
|
|
2169
|
+
reader = new FrameReader();
|
|
2170
|
+
writer;
|
|
2171
|
+
pending = new Map();
|
|
2172
|
+
nextId = 1;
|
|
2173
|
+
nextStreamId = 1;
|
|
2174
|
+
ready;
|
|
2175
|
+
constructor(target){
|
|
2176
|
+
this.target = target;
|
|
2177
|
+
this.ready = this.connect();
|
|
2178
|
+
this.ready.catch(()=>{});
|
|
2179
|
+
}
|
|
2180
|
+
failPending(err) {
|
|
2181
|
+
for (const p of this.pending.values()){
|
|
2182
|
+
clearTimeout(p.timer);
|
|
2183
|
+
p.reject(err);
|
|
2184
|
+
}
|
|
2185
|
+
this.pending.clear();
|
|
2186
|
+
}
|
|
2187
|
+
async connect() {
|
|
2188
|
+
const Bun = globalThis.Bun;
|
|
2189
|
+
if (!Bun?.connect) throw new Error('odblite-socket backend requires Bun (Bun.connect)');
|
|
2190
|
+
const opts = this.target.unix ? {
|
|
2191
|
+
unix: this.target.unix
|
|
2192
|
+
} : {
|
|
2193
|
+
hostname: this.target.hostname ?? '127.0.0.1',
|
|
2194
|
+
port: this.target.port ?? 8787
|
|
2195
|
+
};
|
|
2196
|
+
this.socket = await Bun.connect({
|
|
2197
|
+
...opts,
|
|
2198
|
+
socket: {
|
|
2199
|
+
data: (_s, chunk)=>{
|
|
2200
|
+
for (const frame of this.reader.push(chunk)){
|
|
2201
|
+
const msg = JSON.parse(frame.toString('utf8'));
|
|
2202
|
+
const p = this.pending.get(msg.id);
|
|
2203
|
+
if (p) {
|
|
2204
|
+
this.pending.delete(msg.id);
|
|
2205
|
+
clearTimeout(p.timer);
|
|
2206
|
+
p.resolve(msg.results);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
},
|
|
2210
|
+
drain: (_s)=>this.writer.flush(),
|
|
2211
|
+
close: ()=>{
|
|
2212
|
+
this.socket = void 0;
|
|
2213
|
+
this.reader = new FrameReader();
|
|
2214
|
+
this.failPending(new Error('odblite socket disconnected — request not completed'));
|
|
2215
|
+
},
|
|
2216
|
+
error: (_s, err)=>{
|
|
2217
|
+
this.socket = void 0;
|
|
2218
|
+
this.reader = new FrameReader();
|
|
2219
|
+
this.failPending(err instanceof Error ? err : new Error(`odblite socket error: ${err}`));
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
});
|
|
2223
|
+
this.writer = new FrameWriter(this.socket);
|
|
2224
|
+
}
|
|
2225
|
+
allocStream() {
|
|
2226
|
+
return this.nextStreamId++;
|
|
2227
|
+
}
|
|
2228
|
+
async pipeline(tenant, requests, stream) {
|
|
2229
|
+
await this.ready;
|
|
2230
|
+
if (!this.socket) {
|
|
2231
|
+
this.ready = this.connect();
|
|
2232
|
+
this.ready.catch(()=>{});
|
|
2233
|
+
await this.ready;
|
|
2234
|
+
}
|
|
2235
|
+
const id = this.nextId++;
|
|
2236
|
+
return new Promise((resolve, reject)=>{
|
|
2237
|
+
const timer = setTimeout(()=>{
|
|
2238
|
+
if (this.pending.delete(id)) reject(new Error(`odblite socket request ${id} timed out after ${REQUEST_TIMEOUT_MS}ms`));
|
|
2239
|
+
}, REQUEST_TIMEOUT_MS);
|
|
2240
|
+
timer.unref?.();
|
|
2241
|
+
this.pending.set(id, {
|
|
2242
|
+
resolve,
|
|
2243
|
+
reject,
|
|
2244
|
+
timer
|
|
2245
|
+
});
|
|
2246
|
+
const payload = null != stream ? {
|
|
2247
|
+
id,
|
|
2248
|
+
tenant,
|
|
2249
|
+
stream,
|
|
2250
|
+
requests
|
|
2251
|
+
} : {
|
|
2252
|
+
id,
|
|
2253
|
+
tenant,
|
|
2254
|
+
requests
|
|
2255
|
+
};
|
|
2256
|
+
this.writer.send(Buffer.from(JSON.stringify(payload), 'utf8'));
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
close() {
|
|
2260
|
+
this.socket?.end?.();
|
|
2261
|
+
this.socket = void 0;
|
|
2262
|
+
this.failPending(new Error('odblite socket closed'));
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
function toQueryResult(res) {
|
|
2266
|
+
if ('error' === res.type) throw new Error(res.error.message);
|
|
2267
|
+
const r = res.response.result;
|
|
2268
|
+
const rows = r.rows.map((row)=>{
|
|
2269
|
+
const obj = {};
|
|
2270
|
+
r.cols.forEach((c, i)=>{
|
|
2271
|
+
obj[c.name ?? `c${i}`] = fromHrana(row[i] ?? {
|
|
2272
|
+
type: 'null'
|
|
2273
|
+
});
|
|
2274
|
+
});
|
|
2275
|
+
return obj;
|
|
2276
|
+
});
|
|
2277
|
+
return {
|
|
2278
|
+
rows,
|
|
2279
|
+
rowsAffected: r.affected_row_count,
|
|
2280
|
+
lastInsertRowid: r.last_insert_rowid ? Number(r.last_insert_rowid) : void 0
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
class OdbliteSocketConnection {
|
|
2284
|
+
client;
|
|
2285
|
+
tenant;
|
|
2286
|
+
streamId;
|
|
2287
|
+
databaseName;
|
|
2288
|
+
databaseHash;
|
|
2289
|
+
sql;
|
|
2290
|
+
beginPending = false;
|
|
2291
|
+
constructor(client, tenant, streamId){
|
|
2292
|
+
this.client = client;
|
|
2293
|
+
this.tenant = tenant;
|
|
2294
|
+
this.streamId = streamId;
|
|
2295
|
+
this.databaseName = tenant;
|
|
2296
|
+
this.databaseHash = tenant;
|
|
2297
|
+
const self = this;
|
|
2298
|
+
this.sql = function(strings, ...values) {
|
|
2299
|
+
if (strings && 'object' == typeof strings && 'raw' in strings) {
|
|
2300
|
+
const text = strings.reduce((a, s, i)=>a + s + (i < values.length ? '?' : ''), '');
|
|
2301
|
+
const returns = mightReturnRows(text);
|
|
2302
|
+
return (returns ? self.query(text, values) : self.execute(text, values)).then((r)=>r.rows);
|
|
2303
|
+
}
|
|
2304
|
+
throw new Error('sql() fragment helper not implemented in odblite-socket');
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
async one(requests) {
|
|
2308
|
+
const [res] = await this.client.pipeline(this.tenant, requests);
|
|
2309
|
+
if (!res) throw new Error('odblite-socket: empty pipeline response');
|
|
2310
|
+
return res;
|
|
2311
|
+
}
|
|
2312
|
+
async streamExec(sql, params = []) {
|
|
2313
|
+
const reqs = [];
|
|
2314
|
+
if (this.beginPending) {
|
|
2315
|
+
reqs.push({
|
|
2316
|
+
type: 'execute',
|
|
2317
|
+
stmt: {
|
|
2318
|
+
sql: 'BEGIN'
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
this.beginPending = false;
|
|
2322
|
+
}
|
|
2323
|
+
reqs.push({
|
|
2324
|
+
type: 'execute',
|
|
2325
|
+
stmt: {
|
|
2326
|
+
sql,
|
|
2327
|
+
args: params.map(toHrana)
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2330
|
+
const results = await this.client.pipeline(this.tenant, reqs, this.streamId);
|
|
2331
|
+
if (reqs.length > 1 && results[0]?.type === 'error') throw new Error(results[0].error.message);
|
|
2332
|
+
const res = results[results.length - 1];
|
|
2333
|
+
if (!res) throw new Error('odblite-socket: empty stream response');
|
|
2334
|
+
return toQueryResult(res);
|
|
2335
|
+
}
|
|
2336
|
+
async query(sql, params = []) {
|
|
2337
|
+
if (null != this.streamId) return await this.streamExec(sql, params);
|
|
2338
|
+
return toQueryResult(await this.one([
|
|
2339
|
+
{
|
|
2340
|
+
type: 'execute',
|
|
2341
|
+
stmt: {
|
|
2342
|
+
sql,
|
|
2343
|
+
args: params.map(toHrana)
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
]));
|
|
2347
|
+
}
|
|
2348
|
+
async execute(sql, params = []) {
|
|
2349
|
+
const s = 'string' == typeof sql ? sql : sql.sql;
|
|
2350
|
+
const p = 'string' == typeof sql ? params : sql.args ?? [];
|
|
2351
|
+
if (null != this.streamId) return this.streamExec(s, p);
|
|
2352
|
+
return toQueryResult(await this.one([
|
|
2353
|
+
{
|
|
2354
|
+
type: 'execute',
|
|
2355
|
+
stmt: {
|
|
2356
|
+
sql: s,
|
|
2357
|
+
args: p.map(toHrana)
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
]));
|
|
2361
|
+
}
|
|
2362
|
+
async atomic(steps) {
|
|
2363
|
+
const res = await this.one([
|
|
2364
|
+
{
|
|
2365
|
+
type: 'batch',
|
|
2366
|
+
batch: toBatch(steps)
|
|
2367
|
+
}
|
|
2368
|
+
]);
|
|
2369
|
+
if ('error' === res.type) throw new Error(res.error.message);
|
|
2370
|
+
return res.response.result;
|
|
2371
|
+
}
|
|
2372
|
+
prepare(sql) {
|
|
2373
|
+
return {
|
|
2374
|
+
execute: async (params = [])=>this.execute(sql, params),
|
|
2375
|
+
all: async (params = [])=>(await this.query(sql, params)).rows,
|
|
2376
|
+
get: async (params = [])=>(await this.query(sql, params)).rows[0] ?? null
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
async transaction(fn) {
|
|
2380
|
+
if (null != this.streamId) return fn(this);
|
|
2381
|
+
const tx = new OdbliteSocketConnection(this.client, this.tenant, this.client.allocStream());
|
|
2382
|
+
tx.beginPending = true;
|
|
2383
|
+
try {
|
|
2384
|
+
const out = await fn(tx);
|
|
2385
|
+
if (!tx.beginPending) await tx.streamExec('COMMIT');
|
|
2386
|
+
return out;
|
|
2387
|
+
} catch (e) {
|
|
2388
|
+
if (!tx.beginPending) await tx.streamExec('ROLLBACK').catch(()=>{});
|
|
2389
|
+
throw e;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
begin(fn) {
|
|
2393
|
+
return this.transaction(fn);
|
|
2394
|
+
}
|
|
2395
|
+
async savepoint(fn) {
|
|
2396
|
+
if (null == this.streamId) return this.transaction(fn);
|
|
2397
|
+
const name = `sp_${this.client.allocStream()}`;
|
|
2398
|
+
await this.streamExec(`SAVEPOINT ${name}`);
|
|
2399
|
+
try {
|
|
2400
|
+
const out = await fn(this);
|
|
2401
|
+
await this.streamExec(`RELEASE SAVEPOINT ${name}`);
|
|
2402
|
+
return out;
|
|
2403
|
+
} catch (e) {
|
|
2404
|
+
await this.streamExec(`ROLLBACK TO SAVEPOINT ${name}`).catch(()=>{});
|
|
2405
|
+
throw e;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
async ensureNamespaces(namespaces) {
|
|
2409
|
+
return {
|
|
2410
|
+
success: true,
|
|
2411
|
+
databaseName: this.tenant,
|
|
2412
|
+
namespaces
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
async getNamespaces() {
|
|
2416
|
+
const ns = (await this.query("SELECT name FROM pragma_database_list WHERE name != 'main'")).rows.map((r)=>r.name);
|
|
2417
|
+
return {
|
|
2418
|
+
success: true,
|
|
2419
|
+
databaseName: this.tenant,
|
|
2420
|
+
exists: true,
|
|
2421
|
+
namespaces: ns,
|
|
2422
|
+
registered: true,
|
|
2423
|
+
registeredNamespaces: ns
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
async close() {}
|
|
2427
|
+
}
|
|
2428
|
+
class OdbliteSocketAdapter {
|
|
2429
|
+
type = 'odblite-socket';
|
|
2430
|
+
client;
|
|
2431
|
+
constructor(config){
|
|
2432
|
+
this.client = new SocketClient(config);
|
|
2433
|
+
}
|
|
2434
|
+
async connect(config) {
|
|
2435
|
+
const tenant = config?.databaseHash ?? config?.databaseName;
|
|
2436
|
+
if (!tenant) throw new Error('odblite-socket: databaseHash (or databaseName) required');
|
|
2437
|
+
return new OdbliteSocketConnection(this.client, String(tenant));
|
|
2438
|
+
}
|
|
2439
|
+
async disconnect() {
|
|
2440
|
+
this.client.close();
|
|
2441
|
+
}
|
|
2442
|
+
async isHealthy() {
|
|
2443
|
+
try {
|
|
2444
|
+
const res = await Promise.race([
|
|
2445
|
+
this.client.pipeline('_health', [
|
|
2446
|
+
{
|
|
2447
|
+
type: 'get_version'
|
|
2448
|
+
}
|
|
2449
|
+
]),
|
|
2450
|
+
new Promise((_, rej)=>setTimeout(()=>rej(new Error('probe timeout')), 1500))
|
|
2451
|
+
]);
|
|
2452
|
+
const proto = res?.[0]?.response?.protocol;
|
|
2453
|
+
if (proto !== CLIENT_SOCKET_PROTOCOL) {
|
|
2454
|
+
console.warn(`[odb-client] socket protocol mismatch (server=${proto ?? 'none'}, client=${CLIENT_SOCKET_PROTOCOL}) → using HTTP`);
|
|
2455
|
+
return false;
|
|
2456
|
+
}
|
|
2457
|
+
return true;
|
|
2458
|
+
} catch {
|
|
2459
|
+
return false;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
const CLIENT_SOCKET_PROTOCOL = 2;
|
|
2464
|
+
const DEFAULT_UNIX_SOCKET = '/tmp/odblite.sock';
|
|
2465
|
+
function parseSocketTarget(s) {
|
|
2466
|
+
const v = (s ?? '').trim();
|
|
2467
|
+
if (!v) return {
|
|
2468
|
+
unix: DEFAULT_UNIX_SOCKET
|
|
2469
|
+
};
|
|
2470
|
+
if (v.includes('/')) return {
|
|
2471
|
+
unix: v
|
|
2472
|
+
};
|
|
2473
|
+
const m = v.match(/^(?:([^:]+):)?(\d+)$/);
|
|
2474
|
+
if (m) return {
|
|
2475
|
+
hostname: m[1] || '127.0.0.1',
|
|
2476
|
+
port: Number(m[2])
|
|
2477
|
+
};
|
|
2478
|
+
return {
|
|
2479
|
+
unix: v
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
class OdbliteAutoAdapter {
|
|
2483
|
+
type = 'odblite';
|
|
2484
|
+
opts;
|
|
2485
|
+
chosen;
|
|
2486
|
+
onHttp = false;
|
|
2487
|
+
nextRetryAt = 0;
|
|
2488
|
+
constructor(opts){
|
|
2489
|
+
this.opts = opts;
|
|
2490
|
+
this.chosen = this.pick();
|
|
2491
|
+
}
|
|
2492
|
+
async pick() {
|
|
2493
|
+
const hasBun = void 0 !== globalThis.Bun && !!globalThis.Bun.connect;
|
|
2494
|
+
if (hasBun) {
|
|
2495
|
+
const socket = await this.trySocket();
|
|
2496
|
+
if (socket) return socket;
|
|
2497
|
+
}
|
|
2498
|
+
this.onHttp = true;
|
|
2499
|
+
console.log('[odb-client] transport → HTTP (no router_v2 socket reachable)');
|
|
2500
|
+
return new ODBLiteAdapter(this.opts.http);
|
|
2501
|
+
}
|
|
2502
|
+
async trySocket() {
|
|
2503
|
+
for (const target of this.candidates())try {
|
|
2504
|
+
const socket = new OdbliteSocketAdapter(target);
|
|
2505
|
+
if (await socket.isHealthy()) {
|
|
2506
|
+
console.log(`[odb-client] transport → SOCKET (router_v2 @ ${target.unix ?? `${target.hostname}:${target.port}`})`);
|
|
2507
|
+
return socket;
|
|
2508
|
+
}
|
|
2509
|
+
await socket.disconnect();
|
|
2510
|
+
} catch {}
|
|
2511
|
+
return null;
|
|
2512
|
+
}
|
|
2513
|
+
async maybeUpgrade() {
|
|
2514
|
+
if (!this.onHttp) return;
|
|
2515
|
+
const now = Date.now();
|
|
2516
|
+
if (now < this.nextRetryAt) return;
|
|
2517
|
+
this.nextRetryAt = now + 15000;
|
|
2518
|
+
const socket = await this.trySocket();
|
|
2519
|
+
if (socket) {
|
|
2520
|
+
this.chosen = Promise.resolve(socket);
|
|
2521
|
+
this.onHttp = false;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
candidates() {
|
|
2525
|
+
const { socket, http } = this.opts;
|
|
2526
|
+
if (socket?.unix || socket?.port) return [
|
|
2527
|
+
socket
|
|
2528
|
+
];
|
|
2529
|
+
const env = globalThis.process?.env?.ODB_SOCKET;
|
|
2530
|
+
if (env) return [
|
|
2531
|
+
parseSocketTarget(env)
|
|
2532
|
+
];
|
|
2533
|
+
const out = [
|
|
2534
|
+
{
|
|
2535
|
+
unix: DEFAULT_UNIX_SOCKET
|
|
2536
|
+
}
|
|
2537
|
+
];
|
|
2538
|
+
try {
|
|
2539
|
+
const host = new URL(http?.serviceUrl).hostname;
|
|
2540
|
+
const port = Number(globalThis.process?.env?.ODB_SOCKET_PORT ?? 8787);
|
|
2541
|
+
if (host) out.push({
|
|
2542
|
+
hostname: host,
|
|
2543
|
+
port
|
|
2544
|
+
});
|
|
2545
|
+
} catch {}
|
|
2546
|
+
return out;
|
|
2547
|
+
}
|
|
2548
|
+
async connect(config) {
|
|
2549
|
+
await this.maybeUpgrade();
|
|
2550
|
+
return (await this.chosen).connect(config);
|
|
2551
|
+
}
|
|
2552
|
+
async disconnect(tenantId) {
|
|
2553
|
+
return (await this.chosen).disconnect(tenantId);
|
|
2554
|
+
}
|
|
2555
|
+
async isHealthy() {
|
|
2556
|
+
return (await this.chosen).isHealthy();
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2051
2559
|
function parseSQL(sqlContent, options = {}) {
|
|
2052
2560
|
const separatePragma = options.separatePragma ?? true;
|
|
2053
2561
|
const statements = splitStatements(sqlContent);
|
|
@@ -2157,7 +2665,14 @@ class DatabaseManager {
|
|
|
2157
2665
|
break;
|
|
2158
2666
|
case 'odblite':
|
|
2159
2667
|
if (!config.odblite) throw new Error('odblite config required for odblite backend');
|
|
2160
|
-
this.adapter = new
|
|
2668
|
+
this.adapter = new OdbliteAutoAdapter({
|
|
2669
|
+
socket: config.odbliteSocket,
|
|
2670
|
+
http: config.odblite
|
|
2671
|
+
});
|
|
2672
|
+
break;
|
|
2673
|
+
case 'odblite-socket':
|
|
2674
|
+
if (!config.odbliteSocket) throw new Error('odbliteSocket config required for odblite-socket backend');
|
|
2675
|
+
this.adapter = new OdbliteSocketAdapter(config.odbliteSocket);
|
|
2161
2676
|
break;
|
|
2162
2677
|
default:
|
|
2163
2678
|
throw new Error(`Unknown backend type: ${config.backend}`);
|
|
@@ -2275,6 +2790,12 @@ class DatabaseManager {
|
|
|
2275
2790
|
databaseName: `${this.config.databasePath}_${name}`,
|
|
2276
2791
|
...this.config.odblite
|
|
2277
2792
|
};
|
|
2793
|
+
case 'odblite-socket':
|
|
2794
|
+
return {
|
|
2795
|
+
databaseName: name,
|
|
2796
|
+
databaseHash: name,
|
|
2797
|
+
...this.config.odbliteSocket
|
|
2798
|
+
};
|
|
2278
2799
|
default:
|
|
2279
2800
|
throw new Error(`Unknown backend: ${this.config.backend}`);
|
|
2280
2801
|
}
|
package/package.json
CHANGED