@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/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import * as __rspack_external_async_hooks from "async_hooks";
2
+ import * as __rspack_external_node_async_hooks_e65a2d6c from "node:async_hooks";
1
3
  import { __webpack_require__ } from "./rslib-runtime.js";
2
4
  import { Database } from "bun:sqlite";
3
5
  import { createClient } from "@libsql/client";
@@ -415,6 +417,12 @@ __webpack_require__.add({
415
417
  function createORM(db) {
416
418
  return new ORM(db);
417
419
  }
420
+ },
421
+ async_hooks (module) {
422
+ module.exports = __rspack_external_async_hooks;
423
+ },
424
+ "node:async_hooks" (module) {
425
+ module.exports = __rspack_external_node_async_hooks_e65a2d6c;
418
426
  }
419
427
  });
420
428
  class ODBLiteError extends Error {
@@ -450,12 +458,15 @@ class HTTPClient {
450
458
  };
451
459
  if ('string' == typeof this.config.baseUrl) this.config.baseUrl = this.config.baseUrl.replace(/\/$/, '');
452
460
  }
453
- async query(sql, params = []) {
461
+ async query(sql, params = [], opts) {
454
462
  if (!this.config.databaseId) throw new ConnectionError('No database ID configured. Use setDatabase() first.');
455
463
  const url = `${this.config.baseUrl}/query/${this.config.databaseId}`;
456
464
  const body = {
457
465
  sql,
458
- params
466
+ params,
467
+ ...opts?.txId ? {
468
+ txId: opts.txId
469
+ } : {}
459
470
  };
460
471
  try {
461
472
  const response = await this.makeRequest(url, {
@@ -465,7 +476,7 @@ class HTTPClient {
465
476
  Authorization: `Bearer ${this.config.apiKey}`
466
477
  },
467
478
  body: JSON.stringify(body)
468
- });
479
+ }, opts?.maxRetries);
469
480
  const data = await response.json();
470
481
  if (!data.success) throw new types_QueryError(data.error || 'Query failed', sql, params);
471
482
  let rows = [];
@@ -544,9 +555,10 @@ class HTTPClient {
544
555
  setDatabase(databaseId) {
545
556
  this.config.databaseId = databaseId;
546
557
  }
547
- async makeRequest(url, options) {
558
+ async makeRequest(url, options, maxRetries) {
548
559
  let lastError;
549
- for(let attempt = 1; attempt <= (this.config.retries || 3); attempt++)try {
560
+ const retries = maxRetries ?? this.config.retries ?? 3;
561
+ for(let attempt = 1; attempt <= retries; attempt++)try {
550
562
  const controller = new AbortController();
551
563
  const timeoutId = setTimeout(()=>controller.abort(), this.config.timeout);
552
564
  const response = await fetch(url, {
@@ -570,12 +582,12 @@ class HTTPClient {
570
582
  } catch (error) {
571
583
  lastError = error instanceof Error ? error : new Error('Unknown error');
572
584
  if (error instanceof ConnectionError && error.message.includes('HTTP 4')) throw error;
573
- if (attempt < (this.config.retries || 3)) {
585
+ if (attempt < retries) {
574
586
  const delay = Math.min(1000 * 2 ** (attempt - 1), 10000);
575
587
  await new Promise((resolve)=>setTimeout(resolve, delay));
576
588
  }
577
589
  }
578
- throw new ConnectionError(`Failed after ${this.config.retries} attempts: ${lastError?.message}`, lastError);
590
+ throw new ConnectionError(`Failed after ${retries} attempts: ${lastError?.message}`, lastError);
579
591
  }
580
592
  configure(updates) {
581
593
  return new HTTPClient({
@@ -1048,10 +1060,10 @@ class ODBLiteClient {
1048
1060
  };
1049
1061
  sqlFunction.raw = (text)=>sql_parser_SQLParser.raw(text);
1050
1062
  sqlFunction.identifier = (name)=>sql_parser_SQLParser.identifier(name);
1051
- sqlFunction.query = async (sql, params = [])=>await this.httpClient.query(sql, params);
1052
- sqlFunction.execute = async (sql, args)=>{
1053
- if ('string' == typeof sql) return await this.httpClient.query(sql, args || []);
1054
- return await this.httpClient.query(sql.sql, sql.args || []);
1063
+ sqlFunction.query = async (sql, params = [], opts)=>await this.httpClient.query(sql, params, opts);
1064
+ sqlFunction.execute = async (sql, args, opts)=>{
1065
+ if ('string' == typeof sql) return await this.httpClient.query(sql, args || [], opts);
1066
+ return await this.httpClient.query(sql.sql, sql.args || [], opts);
1055
1067
  };
1056
1068
  sqlFunction.batch = async (statements)=>await this.httpClient.batch(statements);
1057
1069
  sqlFunction.begin = async (modeOrCallback, callback)=>{
@@ -1793,6 +1805,25 @@ class LibSQLConnection {
1793
1805
  return createORM(this);
1794
1806
  }
1795
1807
  }
1808
+ let AsyncLocalStorageCtor = null;
1809
+ try {
1810
+ AsyncLocalStorageCtor = __webpack_require__("node:async_hooks").AsyncLocalStorage;
1811
+ } catch {
1812
+ try {
1813
+ AsyncLocalStorageCtor = __webpack_require__("async_hooks").AsyncLocalStorage;
1814
+ } catch {
1815
+ AsyncLocalStorageCtor = null;
1816
+ }
1817
+ }
1818
+ let txCounter = 0;
1819
+ function newTxId() {
1820
+ try {
1821
+ const c = globalThis.crypto;
1822
+ if (c?.randomUUID) return `tx_${c.randomUUID()}`;
1823
+ } catch {}
1824
+ txCounter = (txCounter + 1) % Number.MAX_SAFE_INTEGER;
1825
+ return `tx_${Date.now().toString(36)}_${txCounter.toString(36)}`;
1826
+ }
1796
1827
  function odblite_parseJsonColumns(rows, jsonColumns) {
1797
1828
  if (!jsonColumns || 0 === jsonColumns.length) return rows;
1798
1829
  return rows.map((row)=>{
@@ -1867,7 +1898,8 @@ class ODBLiteAdapter {
1867
1898
  class ODBLiteConnection {
1868
1899
  client;
1869
1900
  serviceClient;
1870
- inTransaction = false;
1901
+ txStorage = AsyncLocalStorageCtor ? new AsyncLocalStorageCtor() : null;
1902
+ fallbackTxId;
1871
1903
  databaseName;
1872
1904
  databaseHash;
1873
1905
  sql;
@@ -1902,11 +1934,22 @@ class ODBLiteConnection {
1902
1934
  async query(sql, params = [], options) {
1903
1935
  return this.execute(sql, params, options);
1904
1936
  }
1937
+ activeTxId() {
1938
+ return this.txStorage ? this.txStorage.getStore()?.txId : this.fallbackTxId;
1939
+ }
1940
+ requestOpts() {
1941
+ const txId = this.activeTxId();
1942
+ return txId ? {
1943
+ txId,
1944
+ maxRetries: 1
1945
+ } : void 0;
1946
+ }
1905
1947
  async execute(sql, params = [], options) {
1906
1948
  try {
1907
1949
  let rows;
1908
1950
  let rowsAffected;
1909
1951
  let lastInsertRowid;
1952
+ const reqOpts = this.requestOpts();
1910
1953
  if ('object' == typeof sql) {
1911
1954
  if (process.env.DEBUG_SQL) {
1912
1955
  console.log('[ODBLite] Executing SQL:', sql.sql);
@@ -1914,7 +1957,7 @@ class ODBLiteConnection {
1914
1957
  }
1915
1958
  let args = sql.args || [];
1916
1959
  if (options?.stringifyParams) args = odblite_stringifyJsonParams(args, options.stringifyParams);
1917
- const result = await this.client.sql.execute(sql.sql, args);
1960
+ const result = await this.client.sql.execute(sql.sql, args, reqOpts);
1918
1961
  rows = result.rows;
1919
1962
  rowsAffected = result.rowsAffected || 0;
1920
1963
  lastInsertRowid = result.lastInsertRowid;
@@ -1925,7 +1968,7 @@ class ODBLiteConnection {
1925
1968
  }
1926
1969
  let args = params;
1927
1970
  if (options?.stringifyParams) args = odblite_stringifyJsonParams(args, options.stringifyParams);
1928
- const result = await this.client.sql.execute(sql, args);
1971
+ const result = await this.client.sql.execute(sql, args, reqOpts);
1929
1972
  rows = result.rows;
1930
1973
  rowsAffected = result.rowsAffected || 0;
1931
1974
  lastInsertRowid = result.lastInsertRowid;
@@ -1954,27 +1997,34 @@ class ODBLiteConnection {
1954
1997
  };
1955
1998
  }
1956
1999
  async transaction(fn) {
1957
- if (this.inTransaction) return fn(this);
1958
- this.inTransaction = true;
1959
- try {
1960
- await this.execute('BEGIN');
2000
+ if (this.activeTxId()) return fn(this);
2001
+ const txId = newTxId();
2002
+ const run = async ()=>{
2003
+ const prevFallback = this.fallbackTxId;
2004
+ if (!this.txStorage) this.fallbackTxId = txId;
1961
2005
  try {
1962
- const result = await fn(this);
1963
- await this.execute('COMMIT');
1964
- return result;
1965
- } catch (error) {
1966
- await this.execute('ROLLBACK').catch(()=>{});
1967
- throw error;
2006
+ await this.execute('BEGIN');
2007
+ try {
2008
+ const result = await fn(this);
2009
+ await this.execute('COMMIT');
2010
+ return result;
2011
+ } catch (error) {
2012
+ await this.execute('ROLLBACK').catch(()=>{});
2013
+ throw error;
2014
+ }
2015
+ } finally{
2016
+ if (!this.txStorage) this.fallbackTxId = prevFallback;
1968
2017
  }
1969
- } finally{
1970
- this.inTransaction = false;
1971
- }
2018
+ };
2019
+ return this.txStorage ? this.txStorage.run({
2020
+ txId
2021
+ }, run) : run();
1972
2022
  }
1973
2023
  async begin(fn) {
1974
2024
  return this.transaction(fn);
1975
2025
  }
1976
2026
  async savepoint(fn) {
1977
- if (!this.inTransaction) return this.transaction(fn);
2027
+ if (!this.activeTxId()) return this.transaction(fn);
1978
2028
  const name = `sp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1979
2029
  try {
1980
2030
  await this.execute(`SAVEPOINT ${name}`);
@@ -1998,6 +2048,417 @@ class ODBLiteConnection {
1998
2048
  return this.client.httpClient.getNamespaces();
1999
2049
  }
2000
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
+ class FrameWriter {
2118
+ socket;
2119
+ pending = Buffer.alloc(0);
2120
+ constructor(socket){
2121
+ this.socket = socket;
2122
+ }
2123
+ send(payload) {
2124
+ const header = Buffer.allocUnsafe(4);
2125
+ header.writeUInt32LE(payload.length, 0);
2126
+ const frame = Buffer.concat([
2127
+ header,
2128
+ payload
2129
+ ]);
2130
+ this.pending = this.pending.length ? Buffer.concat([
2131
+ this.pending,
2132
+ frame
2133
+ ]) : frame;
2134
+ this.flush();
2135
+ }
2136
+ flush() {
2137
+ while(this.pending.length > 0){
2138
+ const n = this.socket.write(this.pending);
2139
+ if (n <= 0) return;
2140
+ this.pending = n < this.pending.length ? this.pending.subarray(n) : Buffer.alloc(0);
2141
+ }
2142
+ }
2143
+ }
2144
+ class FrameReader {
2145
+ buf = Buffer.alloc(0);
2146
+ push(chunk) {
2147
+ this.buf = this.buf.length ? Buffer.concat([
2148
+ this.buf,
2149
+ chunk
2150
+ ]) : chunk;
2151
+ const out = [];
2152
+ while(this.buf.length >= 4){
2153
+ const len = this.buf.readUInt32LE(0);
2154
+ if (this.buf.length < 4 + len) break;
2155
+ out.push(this.buf.subarray(4, 4 + len));
2156
+ this.buf = this.buf.subarray(4 + len);
2157
+ }
2158
+ return out;
2159
+ }
2160
+ }
2161
+ class SocketClient {
2162
+ target;
2163
+ socket;
2164
+ reader = new FrameReader();
2165
+ writer;
2166
+ pending = new Map();
2167
+ nextId = 1;
2168
+ ready;
2169
+ constructor(target){
2170
+ this.target = target;
2171
+ this.ready = this.connect();
2172
+ this.ready.catch(()=>{});
2173
+ }
2174
+ async connect() {
2175
+ const Bun = globalThis.Bun;
2176
+ if (!Bun?.connect) throw new Error('odblite-socket backend requires Bun (Bun.connect)');
2177
+ const opts = this.target.unix ? {
2178
+ unix: this.target.unix
2179
+ } : {
2180
+ hostname: this.target.hostname ?? '127.0.0.1',
2181
+ port: this.target.port ?? 8787
2182
+ };
2183
+ this.socket = await Bun.connect({
2184
+ ...opts,
2185
+ socket: {
2186
+ data: (_s, chunk)=>{
2187
+ for (const frame of this.reader.push(chunk)){
2188
+ const msg = JSON.parse(frame.toString('utf8'));
2189
+ const resolve = this.pending.get(msg.id);
2190
+ if (resolve) {
2191
+ this.pending.delete(msg.id);
2192
+ resolve(msg.results);
2193
+ }
2194
+ }
2195
+ },
2196
+ drain: (_s)=>this.writer.flush(),
2197
+ close: ()=>{
2198
+ this.socket = void 0;
2199
+ }
2200
+ }
2201
+ });
2202
+ this.writer = new FrameWriter(this.socket);
2203
+ }
2204
+ async pipeline(tenant, requests) {
2205
+ if (!this.socket) {
2206
+ this.ready = this.connect();
2207
+ this.ready.catch(()=>{});
2208
+ }
2209
+ await this.ready;
2210
+ const id = this.nextId++;
2211
+ return new Promise((resolve)=>{
2212
+ this.pending.set(id, resolve);
2213
+ this.writer.send(Buffer.from(JSON.stringify({
2214
+ id,
2215
+ tenant,
2216
+ requests
2217
+ }), 'utf8'));
2218
+ });
2219
+ }
2220
+ close() {
2221
+ this.socket?.end?.();
2222
+ this.socket = void 0;
2223
+ }
2224
+ }
2225
+ function toQueryResult(res) {
2226
+ if ('error' === res.type) throw new Error(res.error.message);
2227
+ const r = res.response.result;
2228
+ const rows = r.rows.map((row)=>{
2229
+ const obj = {};
2230
+ r.cols.forEach((c, i)=>{
2231
+ obj[c.name ?? `c${i}`] = fromHrana(row[i] ?? {
2232
+ type: 'null'
2233
+ });
2234
+ });
2235
+ return obj;
2236
+ });
2237
+ return {
2238
+ rows,
2239
+ rowsAffected: r.affected_row_count,
2240
+ lastInsertRowid: r.last_insert_rowid ? Number(r.last_insert_rowid) : void 0
2241
+ };
2242
+ }
2243
+ class OdbliteSocketConnection {
2244
+ client;
2245
+ tenant;
2246
+ databaseName;
2247
+ databaseHash;
2248
+ sql;
2249
+ buffer;
2250
+ constructor(client, tenant, buffer){
2251
+ this.client = client;
2252
+ this.tenant = tenant;
2253
+ this.databaseName = tenant;
2254
+ this.databaseHash = tenant;
2255
+ this.buffer = buffer;
2256
+ const self = this;
2257
+ this.sql = function(strings, ...values) {
2258
+ if (strings && 'object' == typeof strings && 'raw' in strings) {
2259
+ const text = strings.reduce((a, s, i)=>a + s + (i < values.length ? '?' : ''), '');
2260
+ const returns = /^\s*SELECT/i.test(text) || /\bRETURNING\b/i.test(text);
2261
+ return (returns ? self.query(text, values) : self.execute(text, values)).then((r)=>r.rows);
2262
+ }
2263
+ throw new Error('sql() fragment helper not implemented in odblite-socket');
2264
+ };
2265
+ }
2266
+ async one(requests) {
2267
+ const [res] = await this.client.pipeline(this.tenant, requests);
2268
+ if (!res) throw new Error('odblite-socket: empty pipeline response');
2269
+ return res;
2270
+ }
2271
+ async query(sql, params = []) {
2272
+ return toQueryResult(await this.one([
2273
+ {
2274
+ type: 'execute',
2275
+ stmt: {
2276
+ sql,
2277
+ args: params.map(toHrana)
2278
+ }
2279
+ }
2280
+ ]));
2281
+ }
2282
+ async execute(sql, params = []) {
2283
+ const s = 'string' == typeof sql ? sql : sql.sql;
2284
+ const p = 'string' == typeof sql ? params : sql.args ?? [];
2285
+ if (this.buffer) {
2286
+ this.buffer.push({
2287
+ sql: s,
2288
+ params: p
2289
+ });
2290
+ return {
2291
+ rows: [],
2292
+ rowsAffected: 0
2293
+ };
2294
+ }
2295
+ return toQueryResult(await this.one([
2296
+ {
2297
+ type: 'execute',
2298
+ stmt: {
2299
+ sql: s,
2300
+ args: p.map(toHrana)
2301
+ }
2302
+ }
2303
+ ]));
2304
+ }
2305
+ async atomic(steps) {
2306
+ const res = await this.one([
2307
+ {
2308
+ type: 'batch',
2309
+ batch: toBatch(steps)
2310
+ }
2311
+ ]);
2312
+ if ('error' === res.type) throw new Error(res.error.message);
2313
+ return res.response.result;
2314
+ }
2315
+ prepare(sql) {
2316
+ return {
2317
+ execute: async (params = [])=>this.execute(sql, params),
2318
+ all: async (params = [])=>(await this.query(sql, params)).rows,
2319
+ get: async (params = [])=>(await this.query(sql, params)).rows[0] ?? null
2320
+ };
2321
+ }
2322
+ async transaction(fn) {
2323
+ if (this.buffer) return fn(this);
2324
+ const buffer = [];
2325
+ const out = await fn(new OdbliteSocketConnection(this.client, this.tenant, buffer));
2326
+ if (buffer.length) {
2327
+ const res = await this.atomic(buffer.map((b)=>({
2328
+ sql: b.sql,
2329
+ params: b.params
2330
+ })));
2331
+ const err = res.step_errors?.find((e)=>e);
2332
+ if (err) throw new Error(`transaction batch failed: ${err.message}`);
2333
+ }
2334
+ return out;
2335
+ }
2336
+ begin(fn) {
2337
+ return this.transaction(fn);
2338
+ }
2339
+ savepoint(fn) {
2340
+ return this.transaction(fn);
2341
+ }
2342
+ async ensureNamespaces(namespaces) {
2343
+ return {
2344
+ success: true,
2345
+ databaseName: this.tenant,
2346
+ namespaces
2347
+ };
2348
+ }
2349
+ async getNamespaces() {
2350
+ const ns = (await this.query("SELECT name FROM pragma_database_list WHERE name != 'main'")).rows.map((r)=>r.name);
2351
+ return {
2352
+ success: true,
2353
+ databaseName: this.tenant,
2354
+ exists: true,
2355
+ namespaces: ns,
2356
+ registered: true,
2357
+ registeredNamespaces: ns
2358
+ };
2359
+ }
2360
+ async close() {}
2361
+ }
2362
+ class OdbliteSocketAdapter {
2363
+ type = 'odblite-socket';
2364
+ client;
2365
+ constructor(config){
2366
+ this.client = new SocketClient(config);
2367
+ }
2368
+ async connect(config) {
2369
+ const tenant = config?.databaseHash ?? config?.databaseName;
2370
+ if (!tenant) throw new Error('odblite-socket: databaseHash (or databaseName) required');
2371
+ return new OdbliteSocketConnection(this.client, String(tenant));
2372
+ }
2373
+ async disconnect() {
2374
+ this.client.close();
2375
+ }
2376
+ async isHealthy() {
2377
+ try {
2378
+ await Promise.race([
2379
+ this.client.pipeline('_health', [
2380
+ {
2381
+ type: 'get_autocommit'
2382
+ }
2383
+ ]),
2384
+ new Promise((_, rej)=>setTimeout(()=>rej(new Error('probe timeout')), 1500))
2385
+ ]);
2386
+ return true;
2387
+ } catch {
2388
+ return false;
2389
+ }
2390
+ }
2391
+ }
2392
+ const DEFAULT_UNIX_SOCKET = '/tmp/odblite.sock';
2393
+ function parseSocketTarget(s) {
2394
+ const v = (s ?? '').trim();
2395
+ if (!v) return {
2396
+ unix: DEFAULT_UNIX_SOCKET
2397
+ };
2398
+ if (v.includes('/')) return {
2399
+ unix: v
2400
+ };
2401
+ const m = v.match(/^(?:([^:]+):)?(\d+)$/);
2402
+ if (m) return {
2403
+ hostname: m[1] || '127.0.0.1',
2404
+ port: Number(m[2])
2405
+ };
2406
+ return {
2407
+ unix: v
2408
+ };
2409
+ }
2410
+ class OdbliteAutoAdapter {
2411
+ type = 'odblite';
2412
+ chosen;
2413
+ constructor(opts){
2414
+ this.chosen = this.pick(opts);
2415
+ }
2416
+ async pick(opts) {
2417
+ const hasBun = void 0 !== globalThis.Bun && !!globalThis.Bun.connect;
2418
+ if (hasBun) for (const target of this.candidates(opts))try {
2419
+ const socket = new OdbliteSocketAdapter(target);
2420
+ if (await socket.isHealthy()) {
2421
+ console.log(`[odb-client] transport → SOCKET (router_v2 @ ${target.unix ?? `${target.hostname}:${target.port}`})`);
2422
+ return socket;
2423
+ }
2424
+ await socket.disconnect();
2425
+ } catch {}
2426
+ console.log('[odb-client] transport → HTTP (no router_v2 socket reachable)');
2427
+ return new ODBLiteAdapter(opts.http);
2428
+ }
2429
+ candidates(opts) {
2430
+ if (opts.socket?.unix || opts.socket?.port) return [
2431
+ opts.socket
2432
+ ];
2433
+ const env = globalThis.process?.env?.ODB_SOCKET;
2434
+ if (env) return [
2435
+ parseSocketTarget(env)
2436
+ ];
2437
+ const out = [
2438
+ {
2439
+ unix: DEFAULT_UNIX_SOCKET
2440
+ }
2441
+ ];
2442
+ try {
2443
+ const host = new URL(opts.http?.serviceUrl).hostname;
2444
+ const port = Number(globalThis.process?.env?.ODB_SOCKET_PORT ?? 8787);
2445
+ if (host) out.push({
2446
+ hostname: host,
2447
+ port
2448
+ });
2449
+ } catch {}
2450
+ return out;
2451
+ }
2452
+ async connect(config) {
2453
+ return (await this.chosen).connect(config);
2454
+ }
2455
+ async disconnect(tenantId) {
2456
+ return (await this.chosen).disconnect(tenantId);
2457
+ }
2458
+ async isHealthy() {
2459
+ return (await this.chosen).isHealthy();
2460
+ }
2461
+ }
2001
2462
  function parseSQL(sqlContent, options = {}) {
2002
2463
  const separatePragma = options.separatePragma ?? true;
2003
2464
  const statements = splitStatements(sqlContent);
@@ -2023,6 +2484,8 @@ function splitStatements(sqlContent) {
2023
2484
  let inQuote = false;
2024
2485
  let quoteChar = '';
2025
2486
  let beginEndDepth = 0;
2487
+ let inCte = false;
2488
+ let cteParenDepth = 0;
2026
2489
  const lines = sqlContent.split('\n');
2027
2490
  for (const line of lines){
2028
2491
  let processedLine = '';
@@ -2036,6 +2499,15 @@ function splitStatements(sqlContent) {
2036
2499
  if (beginEndDepth < 0) beginEndDepth = 0;
2037
2500
  }
2038
2501
  }
2502
+ if (!inQuote && lineWithoutComments.trim()) {
2503
+ const withMatch = lineWithoutComments.match(/\bWITH\b.*\bAS\b/i);
2504
+ if (withMatch) inCte = true;
2505
+ if (inCte) for (const char of lineWithoutComments){
2506
+ if ('(' === char) cteParenDepth++;
2507
+ if (')' === char) cteParenDepth--;
2508
+ if (0 === cteParenDepth && ')' === char) inCte = false;
2509
+ }
2510
+ }
2039
2511
  for(let i = 0; i < line.length; i++){
2040
2512
  const char = line[i];
2041
2513
  const prevChar = i > 0 ? line[i - 1] : '';
@@ -2051,7 +2523,7 @@ function splitStatements(sqlContent) {
2051
2523
  quoteChar = char;
2052
2524
  }
2053
2525
  processedLine += char;
2054
- if (';' === char && !inQuote && 0 === beginEndDepth) {
2526
+ if (';' === char && !inQuote && 0 === beginEndDepth && !inCte) {
2055
2527
  currentStatement += processedLine;
2056
2528
  const stmt = currentStatement.trim();
2057
2529
  if (stmt && !stmt.startsWith('--')) statements.push(stmt);
@@ -2096,7 +2568,14 @@ class DatabaseManager {
2096
2568
  break;
2097
2569
  case 'odblite':
2098
2570
  if (!config.odblite) throw new Error('odblite config required for odblite backend');
2099
- this.adapter = new ODBLiteAdapter(config.odblite);
2571
+ this.adapter = new OdbliteAutoAdapter({
2572
+ socket: config.odbliteSocket,
2573
+ http: config.odblite
2574
+ });
2575
+ break;
2576
+ case 'odblite-socket':
2577
+ if (!config.odbliteSocket) throw new Error('odbliteSocket config required for odblite-socket backend');
2578
+ this.adapter = new OdbliteSocketAdapter(config.odbliteSocket);
2100
2579
  break;
2101
2580
  default:
2102
2581
  throw new Error(`Unknown backend type: ${config.backend}`);
@@ -2214,6 +2693,12 @@ class DatabaseManager {
2214
2693
  databaseName: `${this.config.databasePath}_${name}`,
2215
2694
  ...this.config.odblite
2216
2695
  };
2696
+ case 'odblite-socket':
2697
+ return {
2698
+ databaseName: name,
2699
+ databaseHash: name,
2700
+ ...this.config.odbliteSocket
2701
+ };
2217
2702
  default:
2218
2703
  throw new Error(`Unknown backend: ${this.config.backend}`);
2219
2704
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pineliner/odb-client",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "Isomorphic client for ODB-Lite with postgres.js-like template string SQL support",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -14,7 +14,7 @@
14
14
  "deploy": "bun run build && npm publish --access public"
15
15
  },
16
16
  "dependencies": {
17
- "@libsql/client": "^0.17.0",
17
+ "@libsql/client": "^0.17.3",
18
18
  "node-sql-parser": "^5.3.12"
19
19
  },
20
20
  "devDependencies": {