@pineliner/odb-client 1.2.0 → 1.5.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.
@@ -10,6 +10,10 @@ export declare class OdbliteSocketAdapter implements DatabaseAdapter {
10
10
  disconnect(): Promise<void>;
11
11
  isHealthy(): Promise<boolean>;
12
12
  }
13
+ /** Socket wire-protocol version this client speaks. The connect-time handshake (isHealthy → get_version)
14
+ * requires the server to match; on mismatch the auto-adapter falls back to HTTP. Keep in lockstep with
15
+ * router_v2's SOCKET_PROTOCOL. */
16
+ export declare const CLIENT_SOCKET_PROTOCOL = 2;
13
17
  /** Default same-host unix socket (free — v1 only uses /tmp/odblite-internal.sock). */
14
18
  export declare const DEFAULT_UNIX_SOCKET = "/tmp/odblite.sock";
15
19
  /**
@@ -36,12 +40,21 @@ export declare function parseSocketTarget(s?: string): SocketTarget;
36
40
  */
37
41
  export declare class OdbliteAutoAdapter implements DatabaseAdapter {
38
42
  readonly type: BackendType;
39
- private readonly chosen;
43
+ private readonly opts;
44
+ private chosen;
45
+ private onHttp;
46
+ private nextRetryAt;
40
47
  constructor(opts: {
41
48
  socket?: OdbliteSocketConfig;
42
49
  http: any;
43
50
  });
44
51
  private pick;
52
+ /** Probe the cascade once; return a live socket adapter or null. */
53
+ private trySocket;
54
+ /** When we fell back to HTTP, re-probe the socket on a cooldown so a socket that
55
+ * comes up LATER (server started after the app) is picked up without a restart —
56
+ * subsequent connections then use it. */
57
+ private maybeUpgrade;
45
58
  /** Ordered socket targets to probe. Explicit override → a single target; else the
46
59
  * zero-config cascade unix (same host) → TCP (host derived from ODB_LITE_URL). */
47
60
  private candidates;
@@ -1 +1 @@
1
- {"version":3,"file":"odblite-socket.d.ts","sourceRoot":"","sources":["../../../src/database/adapters/odblite-socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAkC,MAAM,UAAU,CAAA;AAExG,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAoIjE,MAAM,WAAW,mBAAoB,SAAQ,YAAY;CAAG;AAE5D,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,MAAM,EAAE,mBAAmB;IAIjC,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC;IAMzC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAWpC;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,sBAAsB,CAAA;AAEtD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAO1D;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;gBAErC,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,mBAAmB,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE;YAI/C,IAAI;IAoBlB;uFACmF;IACnF,OAAO,CAAC,UAAU;IAeZ,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC;IAGzC,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG5C,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
1
+ {"version":3,"file":"odblite-socket.d.ts","sourceRoot":"","sources":["../../../src/database/adapters/odblite-socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAkC,MAAM,UAAU,CAAA;AAExG,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AA0KjE,MAAM,WAAW,mBAAoB,SAAQ,YAAY;CAAG;AAE5D,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,MAAM,EAAE,mBAAmB;IAIjC,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC;IAMzC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAmBpC;AAED;;mCAEmC;AACnC,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,sBAAsB,CAAA;AAEtD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAO1D;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6C;IAClE,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,WAAW,CAAI;gBAEX,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,mBAAmB,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE;YAK/C,IAAI;IAWlB,oEAAoE;YACtD,SAAS;IAgBvB;;8CAE0C;YAC5B,YAAY;IAY1B;uFACmF;IACnF,OAAO,CAAC,UAAU;IAgBZ,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC;IAIzC,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG5C,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
@@ -6,6 +6,11 @@ import { type StreamRequest, type StreamResult } from './protocol';
6
6
  *
7
7
  * Bun-only (uses `Bun.connect`). app-router runs on Bun; the HTTP `odblite`
8
8
  * backend remains the universal fallback for non-Bun environments.
9
+ *
10
+ * Liveness invariant: an in-flight request must ALWAYS terminate. If the
11
+ * connection drops (close/error) or a reply never arrives, the pending promise
12
+ * is REJECTED — never left hanging. A hung pending request would wedge the
13
+ * caller's transaction, hold its write lane, and cascade into a 30s timeout.
9
14
  */
10
15
  export interface SocketTarget {
11
16
  unix?: string;
@@ -19,10 +24,22 @@ export declare class SocketClient {
19
24
  private writer;
20
25
  private readonly pending;
21
26
  private nextId;
27
+ private nextStreamId;
22
28
  private ready;
23
29
  constructor(target: SocketTarget);
30
+ /** Reject + clear every in-flight request — called on disconnect/error/close so a
31
+ * dropped connection FAILS its requests (the caller retries) instead of hanging. */
32
+ private failPending;
24
33
  private connect;
25
- pipeline(tenant: string, requests: StreamRequest[]): Promise<StreamResult[]>;
34
+ /** Reconnect with bounded backoff — bridges a router restart so a request landing in the down-window
35
+ * waits it out instead of failing. After the last attempt the error propagates (caller retries / the
36
+ * auto-adapter can fall back to HTTP). Backoff: 50·100·200·400·800·1600ms ≈ 3s total by default. */
37
+ private connectWithRetry;
38
+ /** Allocate a fresh stream id for an interactive transaction (unique per connection). The server
39
+ * pins one SQLite connection per stream id, so all of a tx's statements run on it (read-your-own-
40
+ * writes). The id is carried in every frame of that tx via pipeline()'s `stream` arg. */
41
+ allocStream(): number;
42
+ pipeline(tenant: string, requests: StreamRequest[], stream?: number): Promise<StreamResult[]>;
26
43
  close(): void;
27
44
  }
28
45
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/database/adapters/socket/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAE5F;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,qBAAa,YAAY;IAQX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiD;IACzE,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,KAAK,CAAe;gBAEC,MAAM,EAAE,YAAY;YAOnC,OAAO;IA6Bf,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAalF,KAAK,IAAI,IAAI;CAId"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/database/adapters/socket/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAE5F;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAqBD,qBAAa,YAAY;IASX,OAAO,CAAC,QAAQ,CAAC,MAAM;IARnC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,KAAK,CAAe;gBAEC,MAAM,EAAE,YAAY;IAOjD;yFACqF;IACrF,OAAO,CAAC,WAAW;YAQL,OAAO;IAsDrB;;yGAEqG;YACvF,gBAAgB;IAc9B;;8FAE0F;IAC1F,WAAW,IAAI,MAAM;IAIf,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA0BnG,KAAK,IAAI,IAAI;CAKd"}
@@ -95,6 +95,8 @@ export type StreamRequest = {
95
95
  sql: string;
96
96
  } | {
97
97
  type: 'get_autocommit';
98
+ } | {
99
+ type: 'get_version';
98
100
  } | {
99
101
  type: 'close';
100
102
  };
@@ -111,6 +113,7 @@ export interface AtomicStep {
111
113
  expect?: 'one' | 'atLeastOne' | number;
112
114
  }
113
115
  export declare function toBatch(steps: AtomicStep[]): Batch;
116
+ export declare function mightReturnRows(sql: string): boolean;
114
117
  interface Writable {
115
118
  write(data: Uint8Array): number;
116
119
  }
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../src/database/adapters/socket/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../src/database/adapters/socket/protocol.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -2204,6 +2204,10 @@ var __webpack_exports__ = {};
2204
2204
  }))
2205
2205
  };
2206
2206
  }
2207
+ const READ_ONLY_RE = /^\s*(?:SELECT|EXPLAIN|VALUES)\b/i;
2208
+ function mightReturnRows(sql) {
2209
+ return READ_ONLY_RE.test(sql) || /\bRETURNING\b/i.test(sql);
2210
+ }
2207
2211
  class FrameWriter {
2208
2212
  socket;
2209
2213
  pending = Buffer.alloc(0);
@@ -2231,6 +2235,7 @@ var __webpack_exports__ = {};
2231
2235
  }
2232
2236
  }
2233
2237
  }
2238
+ const MAX_FRAME = 1024 * (Number(globalThis.process?.env?.ODB_MAX_FRAME_MB) || 64) * 1024;
2234
2239
  class FrameReader {
2235
2240
  buf = Buffer.alloc(0);
2236
2241
  push(chunk) {
@@ -2241,6 +2246,7 @@ var __webpack_exports__ = {};
2241
2246
  const out = [];
2242
2247
  while(this.buf.length >= 4){
2243
2248
  const len = this.buf.readUInt32LE(0);
2249
+ if (len > MAX_FRAME) throw new Error(`odblite reply frame too large: ${len} bytes (max ${MAX_FRAME})`);
2244
2250
  if (this.buf.length < 4 + len) break;
2245
2251
  out.push(this.buf.subarray(4, 4 + len));
2246
2252
  this.buf = this.buf.subarray(4 + len);
@@ -2248,6 +2254,10 @@ var __webpack_exports__ = {};
2248
2254
  return out;
2249
2255
  }
2250
2256
  }
2257
+ const REQUEST_TIMEOUT_MS = Number(globalThis.process?.env?.ODB_SOCKET_REQUEST_TIMEOUT_MS) || 30000;
2258
+ const RECONNECT_ATTEMPTS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_ATTEMPTS) || 6;
2259
+ const RECONNECT_BASE_MS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_BASE_MS) || 50;
2260
+ const sleep = (ms)=>new Promise((r)=>setTimeout(r, ms));
2251
2261
  class SocketClient {
2252
2262
  target;
2253
2263
  socket;
@@ -2255,12 +2265,20 @@ var __webpack_exports__ = {};
2255
2265
  writer;
2256
2266
  pending = new Map();
2257
2267
  nextId = 1;
2268
+ nextStreamId = 1;
2258
2269
  ready;
2259
2270
  constructor(target){
2260
2271
  this.target = target;
2261
2272
  this.ready = this.connect();
2262
2273
  this.ready.catch(()=>{});
2263
2274
  }
2275
+ failPending(err) {
2276
+ for (const p of this.pending.values()){
2277
+ clearTimeout(p.timer);
2278
+ p.reject(err);
2279
+ }
2280
+ this.pending.clear();
2281
+ }
2264
2282
  async connect() {
2265
2283
  const Bun = globalThis.Bun;
2266
2284
  if (!Bun?.connect) throw new Error('odblite-socket backend requires Bun (Bun.connect)');
@@ -2273,43 +2291,93 @@ var __webpack_exports__ = {};
2273
2291
  this.socket = await Bun.connect({
2274
2292
  ...opts,
2275
2293
  socket: {
2276
- data: (_s, chunk)=>{
2277
- for (const frame of this.reader.push(chunk)){
2294
+ data: (s, chunk)=>{
2295
+ let frames;
2296
+ try {
2297
+ frames = this.reader.push(chunk);
2298
+ } catch (err) {
2299
+ this.socket = void 0;
2300
+ this.reader = new FrameReader();
2301
+ try {
2302
+ s.end?.();
2303
+ } catch {}
2304
+ this.failPending(err instanceof Error ? err : new Error(`odblite framing error: ${err}`));
2305
+ return;
2306
+ }
2307
+ for (const frame of frames){
2278
2308
  const msg = JSON.parse(frame.toString('utf8'));
2279
- const resolve = this.pending.get(msg.id);
2280
- if (resolve) {
2309
+ const p = this.pending.get(msg.id);
2310
+ if (p) {
2281
2311
  this.pending.delete(msg.id);
2282
- resolve(msg.results);
2312
+ clearTimeout(p.timer);
2313
+ p.resolve(msg.results);
2283
2314
  }
2284
2315
  }
2285
2316
  },
2286
2317
  drain: (_s)=>this.writer.flush(),
2287
2318
  close: ()=>{
2288
2319
  this.socket = void 0;
2320
+ this.reader = new FrameReader();
2321
+ this.failPending(new Error('odblite socket disconnected — request not completed'));
2322
+ },
2323
+ error: (_s, err)=>{
2324
+ this.socket = void 0;
2325
+ this.reader = new FrameReader();
2326
+ this.failPending(err instanceof Error ? err : new Error(`odblite socket error: ${err}`));
2289
2327
  }
2290
2328
  }
2291
2329
  });
2292
2330
  this.writer = new FrameWriter(this.socket);
2293
2331
  }
2294
- async pipeline(tenant, requests) {
2332
+ async connectWithRetry() {
2333
+ let lastErr;
2334
+ for(let attempt = 0; attempt < RECONNECT_ATTEMPTS; attempt++)try {
2335
+ await this.connect();
2336
+ return;
2337
+ } catch (err) {
2338
+ lastErr = err;
2339
+ if (attempt < RECONNECT_ATTEMPTS - 1) await sleep(RECONNECT_BASE_MS * 2 ** attempt);
2340
+ }
2341
+ throw lastErr instanceof Error ? lastErr : new Error(`odblite socket reconnect failed: ${lastErr}`);
2342
+ }
2343
+ allocStream() {
2344
+ return this.nextStreamId++;
2345
+ }
2346
+ async pipeline(tenant, requests, stream) {
2347
+ await this.ready.catch(()=>{});
2295
2348
  if (!this.socket) {
2296
- this.ready = this.connect();
2349
+ this.ready = this.connectWithRetry();
2297
2350
  this.ready.catch(()=>{});
2351
+ await this.ready;
2298
2352
  }
2299
- await this.ready;
2300
2353
  const id = this.nextId++;
2301
- return new Promise((resolve)=>{
2302
- this.pending.set(id, resolve);
2303
- this.writer.send(Buffer.from(JSON.stringify({
2354
+ return new Promise((resolve, reject)=>{
2355
+ const timer = setTimeout(()=>{
2356
+ if (this.pending.delete(id)) reject(new Error(`odblite socket request ${id} timed out after ${REQUEST_TIMEOUT_MS}ms`));
2357
+ }, REQUEST_TIMEOUT_MS);
2358
+ timer.unref?.();
2359
+ this.pending.set(id, {
2360
+ resolve,
2361
+ reject,
2362
+ timer
2363
+ });
2364
+ const payload = null != stream ? {
2304
2365
  id,
2305
2366
  tenant,
2367
+ stream,
2306
2368
  requests
2307
- }), 'utf8'));
2369
+ } : {
2370
+ id,
2371
+ tenant,
2372
+ requests
2373
+ };
2374
+ this.writer.send(Buffer.from(JSON.stringify(payload), 'utf8'));
2308
2375
  });
2309
2376
  }
2310
2377
  close() {
2311
2378
  this.socket?.end?.();
2312
2379
  this.socket = void 0;
2380
+ this.failPending(new Error('odblite socket closed'));
2313
2381
  }
2314
2382
  }
2315
2383
  function toQueryResult(res) {
@@ -2333,21 +2401,22 @@ var __webpack_exports__ = {};
2333
2401
  class OdbliteSocketConnection {
2334
2402
  client;
2335
2403
  tenant;
2404
+ streamId;
2336
2405
  databaseName;
2337
2406
  databaseHash;
2338
2407
  sql;
2339
- buffer;
2340
- constructor(client, tenant, buffer){
2408
+ beginPending = false;
2409
+ constructor(client, tenant, streamId){
2341
2410
  this.client = client;
2342
2411
  this.tenant = tenant;
2412
+ this.streamId = streamId;
2343
2413
  this.databaseName = tenant;
2344
2414
  this.databaseHash = tenant;
2345
- this.buffer = buffer;
2346
2415
  const self = this;
2347
2416
  this.sql = function(strings, ...values) {
2348
2417
  if (strings && 'object' == typeof strings && 'raw' in strings) {
2349
2418
  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);
2419
+ const returns = mightReturnRows(text);
2351
2420
  return (returns ? self.query(text, values) : self.execute(text, values)).then((r)=>r.rows);
2352
2421
  }
2353
2422
  throw new Error('sql() fragment helper not implemented in odblite-socket');
@@ -2358,7 +2427,32 @@ var __webpack_exports__ = {};
2358
2427
  if (!res) throw new Error('odblite-socket: empty pipeline response');
2359
2428
  return res;
2360
2429
  }
2430
+ async streamExec(sql, params = []) {
2431
+ const reqs = [];
2432
+ if (this.beginPending) {
2433
+ reqs.push({
2434
+ type: 'execute',
2435
+ stmt: {
2436
+ sql: 'BEGIN'
2437
+ }
2438
+ });
2439
+ this.beginPending = false;
2440
+ }
2441
+ reqs.push({
2442
+ type: 'execute',
2443
+ stmt: {
2444
+ sql,
2445
+ args: params.map(toHrana)
2446
+ }
2447
+ });
2448
+ const results = await this.client.pipeline(this.tenant, reqs, this.streamId);
2449
+ if (reqs.length > 1 && results[0]?.type === 'error') throw new Error(results[0].error.message);
2450
+ const res = results[results.length - 1];
2451
+ if (!res) throw new Error('odblite-socket: empty stream response');
2452
+ return toQueryResult(res);
2453
+ }
2361
2454
  async query(sql, params = []) {
2455
+ if (null != this.streamId) return await this.streamExec(sql, params);
2362
2456
  return toQueryResult(await this.one([
2363
2457
  {
2364
2458
  type: 'execute',
@@ -2372,16 +2466,7 @@ var __webpack_exports__ = {};
2372
2466
  async execute(sql, params = []) {
2373
2467
  const s = 'string' == typeof sql ? sql : sql.sql;
2374
2468
  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
- }
2469
+ if (null != this.streamId) return this.streamExec(s, p);
2385
2470
  return toQueryResult(await this.one([
2386
2471
  {
2387
2472
  type: 'execute',
@@ -2410,24 +2495,33 @@ var __webpack_exports__ = {};
2410
2495
  };
2411
2496
  }
2412
2497
  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}`);
2498
+ if (null != this.streamId) return fn(this);
2499
+ const tx = new OdbliteSocketConnection(this.client, this.tenant, this.client.allocStream());
2500
+ tx.beginPending = true;
2501
+ try {
2502
+ const out = await fn(tx);
2503
+ if (!tx.beginPending) await tx.streamExec('COMMIT');
2504
+ return out;
2505
+ } catch (e) {
2506
+ if (!tx.beginPending) await tx.streamExec('ROLLBACK').catch(()=>{});
2507
+ throw e;
2423
2508
  }
2424
- return out;
2425
2509
  }
2426
2510
  begin(fn) {
2427
2511
  return this.transaction(fn);
2428
2512
  }
2429
- savepoint(fn) {
2430
- return this.transaction(fn);
2513
+ async savepoint(fn) {
2514
+ if (null == this.streamId) return this.transaction(fn);
2515
+ const name = `sp_${this.client.allocStream()}`;
2516
+ await this.streamExec(`SAVEPOINT ${name}`);
2517
+ try {
2518
+ const out = await fn(this);
2519
+ await this.streamExec(`RELEASE SAVEPOINT ${name}`);
2520
+ return out;
2521
+ } catch (e) {
2522
+ await this.streamExec(`ROLLBACK TO SAVEPOINT ${name}`).catch(()=>{});
2523
+ throw e;
2524
+ }
2431
2525
  }
2432
2526
  async ensureNamespaces(namespaces) {
2433
2527
  return {
@@ -2465,20 +2559,26 @@ var __webpack_exports__ = {};
2465
2559
  }
2466
2560
  async isHealthy() {
2467
2561
  try {
2468
- await Promise.race([
2562
+ const res = await Promise.race([
2469
2563
  this.client.pipeline('_health', [
2470
2564
  {
2471
- type: 'get_autocommit'
2565
+ type: 'get_version'
2472
2566
  }
2473
2567
  ]),
2474
2568
  new Promise((_, rej)=>setTimeout(()=>rej(new Error('probe timeout')), 1500))
2475
2569
  ]);
2570
+ const proto = res?.[0]?.response?.protocol;
2571
+ if (proto !== CLIENT_SOCKET_PROTOCOL) {
2572
+ console.warn(`[odb-client] socket protocol mismatch (server=${proto ?? 'none'}, client=${CLIENT_SOCKET_PROTOCOL}) → using HTTP`);
2573
+ return false;
2574
+ }
2476
2575
  return true;
2477
2576
  } catch {
2478
2577
  return false;
2479
2578
  }
2480
2579
  }
2481
2580
  }
2581
+ const CLIENT_SOCKET_PROTOCOL = 2;
2482
2582
  const DEFAULT_UNIX_SOCKET = '/tmp/odblite.sock';
2483
2583
  function parseSocketTarget(s) {
2484
2584
  const v = (s ?? '').trim();
@@ -2499,13 +2599,26 @@ var __webpack_exports__ = {};
2499
2599
  }
2500
2600
  class OdbliteAutoAdapter {
2501
2601
  type = 'odblite';
2602
+ opts;
2502
2603
  chosen;
2604
+ onHttp = false;
2605
+ nextRetryAt = 0;
2503
2606
  constructor(opts){
2504
- this.chosen = this.pick(opts);
2607
+ this.opts = opts;
2608
+ this.chosen = this.pick();
2505
2609
  }
2506
- async pick(opts) {
2610
+ async pick() {
2507
2611
  const hasBun = void 0 !== globalThis.Bun && !!globalThis.Bun.connect;
2508
- if (hasBun) for (const target of this.candidates(opts))try {
2612
+ if (hasBun) {
2613
+ const socket = await this.trySocket();
2614
+ if (socket) return socket;
2615
+ }
2616
+ this.onHttp = true;
2617
+ console.log('[odb-client] transport → HTTP (no router_v2 socket reachable)');
2618
+ return new ODBLiteAdapter(this.opts.http);
2619
+ }
2620
+ async trySocket() {
2621
+ for (const target of this.candidates())try {
2509
2622
  const socket = new OdbliteSocketAdapter(target);
2510
2623
  if (await socket.isHealthy()) {
2511
2624
  console.log(`[odb-client] transport → SOCKET (router_v2 @ ${target.unix ?? `${target.hostname}:${target.port}`})`);
@@ -2513,12 +2626,23 @@ var __webpack_exports__ = {};
2513
2626
  }
2514
2627
  await socket.disconnect();
2515
2628
  } 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
2629
+ return null;
2630
+ }
2631
+ async maybeUpgrade() {
2632
+ if (!this.onHttp) return;
2633
+ const now = Date.now();
2634
+ if (now < this.nextRetryAt) return;
2635
+ this.nextRetryAt = now + 15000;
2636
+ const socket = await this.trySocket();
2637
+ if (socket) {
2638
+ this.chosen = Promise.resolve(socket);
2639
+ this.onHttp = false;
2640
+ }
2641
+ }
2642
+ candidates() {
2643
+ const { socket, http } = this.opts;
2644
+ if (socket?.unix || socket?.port) return [
2645
+ socket
2522
2646
  ];
2523
2647
  const env = globalThis.process?.env?.ODB_SOCKET;
2524
2648
  if (env) return [
@@ -2530,7 +2654,7 @@ var __webpack_exports__ = {};
2530
2654
  }
2531
2655
  ];
2532
2656
  try {
2533
- const host = new URL(opts.http?.serviceUrl).hostname;
2657
+ const host = new URL(http?.serviceUrl).hostname;
2534
2658
  const port = Number(globalThis.process?.env?.ODB_SOCKET_PORT ?? 8787);
2535
2659
  if (host) out.push({
2536
2660
  hostname: host,
@@ -2540,6 +2664,7 @@ var __webpack_exports__ = {};
2540
2664
  return out;
2541
2665
  }
2542
2666
  async connect(config) {
2667
+ await this.maybeUpgrade();
2543
2668
  return (await this.chosen).connect(config);
2544
2669
  }
2545
2670
  async disconnect(tenantId) {