@pineliner/odb-client 1.5.2 → 1.5.3

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.
@@ -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;AA+KjE,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"}
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,EAAkD,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AA8LnG,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"}
@@ -17,6 +17,15 @@ export interface SocketTarget {
17
17
  hostname?: string;
18
18
  port?: number;
19
19
  }
20
+ /** An interactive transaction was pinned to a socket connection that has since been reset (router
21
+ * restart / socket drop). The tx's server-side connection — and its uncommitted writes — are gone, so
22
+ * it CANNOT be resumed on a fresh connection; the caller must retry the whole transaction. Thrown
23
+ * instead of silently reconnecting and sending a continuation frame on the new connection (which the
24
+ * server rejects as "stream N is not open" — a confusing, non-actionable error). */
25
+ export declare class InteractiveTxConnectionLostError extends Error {
26
+ readonly code = "ODB_TX_CONNECTION_LOST";
27
+ constructor();
28
+ }
20
29
  export declare class SocketClient {
21
30
  private readonly target;
22
31
  private socket;
@@ -25,12 +34,18 @@ export declare class SocketClient {
25
34
  private readonly pending;
26
35
  private nextId;
27
36
  private nextStreamId;
37
+ private epoch;
28
38
  private ready;
29
39
  constructor(target: SocketTarget);
30
40
  /** Reject + clear every in-flight request — called on disconnect/error/close so a
31
41
  * dropped connection FAILS its requests (the caller retries) instead of hanging. */
32
42
  private failPending;
33
43
  private connect;
44
+ /** Current connection generation — an interactive tx captures this when it opens (its BEGIN lands). */
45
+ get connectionEpoch(): number;
46
+ /** True only if the live socket is the SAME generation the tx opened on. False after any drop (socket
47
+ * cleared) or reconnect (epoch bumped) — the tx's pinned server connection is gone either way. */
48
+ isConnectionLive(epoch: number | undefined): boolean;
34
49
  /** Reconnect with bounded backoff — bridges a router restart so a request landing in the down-window
35
50
  * waits it out instead of failing. After the last attempt the error propagates (caller retries / the
36
51
  * auto-adapter can fall back to HTTP). Backoff: 50·100·200·400·800·1600ms ≈ 3s total by default. */
@@ -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;;;;;;;;;;;;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"}
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;;;;qFAIqF;AACrF,qBAAa,gCAAiC,SAAQ,KAAK;IACzD,QAAQ,CAAC,IAAI,4BAA2B;;CAKzC;AAED,qBAAa,YAAY;IAYX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAXnC,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;IAGxB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,KAAK,CAAe;gBAEC,MAAM,EAAE,YAAY;IAOjD;yFACqF;IACrF,OAAO,CAAC,WAAW;YAQL,OAAO;IAuDrB,uGAAuG;IACvG,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED;uGACmG;IACnG,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO;IAIpD;;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"}
package/dist/index.cjs CHANGED
@@ -2258,6 +2258,13 @@ var __webpack_exports__ = {};
2258
2258
  const RECONNECT_ATTEMPTS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_ATTEMPTS) || 6;
2259
2259
  const RECONNECT_BASE_MS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_BASE_MS) || 50;
2260
2260
  const sleep = (ms)=>new Promise((r)=>setTimeout(r, ms));
2261
+ class InteractiveTxConnectionLostError extends Error {
2262
+ code = 'ODB_TX_CONNECTION_LOST';
2263
+ constructor(){
2264
+ super('odblite: interactive transaction lost its connection (router restart / socket reset) — retry the transaction');
2265
+ this.name = 'InteractiveTxConnectionLostError';
2266
+ }
2267
+ }
2261
2268
  class SocketClient {
2262
2269
  target;
2263
2270
  socket;
@@ -2266,6 +2273,7 @@ var __webpack_exports__ = {};
2266
2273
  pending = new Map();
2267
2274
  nextId = 1;
2268
2275
  nextStreamId = 1;
2276
+ epoch = 0;
2269
2277
  ready;
2270
2278
  constructor(target){
2271
2279
  this.target = target;
@@ -2328,6 +2336,13 @@ var __webpack_exports__ = {};
2328
2336
  }
2329
2337
  });
2330
2338
  this.writer = new FrameWriter(this.socket);
2339
+ this.epoch++;
2340
+ }
2341
+ get connectionEpoch() {
2342
+ return this.epoch;
2343
+ }
2344
+ isConnectionLive(epoch) {
2345
+ return !!this.socket && this.epoch === epoch;
2331
2346
  }
2332
2347
  async connectWithRetry() {
2333
2348
  let lastErr;
@@ -2406,6 +2421,7 @@ var __webpack_exports__ = {};
2406
2421
  databaseHash;
2407
2422
  sql;
2408
2423
  beginPending = false;
2424
+ openEpoch;
2409
2425
  constructor(client, tenant, streamId){
2410
2426
  this.client = client;
2411
2427
  this.tenant = tenant;
@@ -2428,6 +2444,8 @@ var __webpack_exports__ = {};
2428
2444
  return res;
2429
2445
  }
2430
2446
  async streamExec(sql, params = []) {
2447
+ const opening = this.beginPending;
2448
+ if (!opening && !this.client.isConnectionLive(this.openEpoch)) throw new InteractiveTxConnectionLostError();
2431
2449
  const reqs = [];
2432
2450
  if (this.beginPending) {
2433
2451
  reqs.push({
@@ -2446,6 +2464,7 @@ var __webpack_exports__ = {};
2446
2464
  }
2447
2465
  });
2448
2466
  const results = await this.client.pipeline(this.tenant, reqs, this.streamId);
2467
+ if (opening) this.openEpoch = this.client.connectionEpoch;
2449
2468
  if (reqs.length > 1 && results[0]?.type === 'error') throw new Error(results[0].error.message);
2450
2469
  const res = results[results.length - 1];
2451
2470
  if (!res) throw new Error('odblite-socket: empty stream response');
package/dist/index.js CHANGED
@@ -2168,6 +2168,13 @@ const REQUEST_TIMEOUT_MS = Number(globalThis.process?.env?.ODB_SOCKET_REQUEST_TI
2168
2168
  const RECONNECT_ATTEMPTS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_ATTEMPTS) || 6;
2169
2169
  const RECONNECT_BASE_MS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_BASE_MS) || 50;
2170
2170
  const sleep = (ms)=>new Promise((r)=>setTimeout(r, ms));
2171
+ class InteractiveTxConnectionLostError extends Error {
2172
+ code = 'ODB_TX_CONNECTION_LOST';
2173
+ constructor(){
2174
+ super('odblite: interactive transaction lost its connection (router restart / socket reset) — retry the transaction');
2175
+ this.name = 'InteractiveTxConnectionLostError';
2176
+ }
2177
+ }
2171
2178
  class SocketClient {
2172
2179
  target;
2173
2180
  socket;
@@ -2176,6 +2183,7 @@ class SocketClient {
2176
2183
  pending = new Map();
2177
2184
  nextId = 1;
2178
2185
  nextStreamId = 1;
2186
+ epoch = 0;
2179
2187
  ready;
2180
2188
  constructor(target){
2181
2189
  this.target = target;
@@ -2238,6 +2246,13 @@ class SocketClient {
2238
2246
  }
2239
2247
  });
2240
2248
  this.writer = new FrameWriter(this.socket);
2249
+ this.epoch++;
2250
+ }
2251
+ get connectionEpoch() {
2252
+ return this.epoch;
2253
+ }
2254
+ isConnectionLive(epoch) {
2255
+ return !!this.socket && this.epoch === epoch;
2241
2256
  }
2242
2257
  async connectWithRetry() {
2243
2258
  let lastErr;
@@ -2316,6 +2331,7 @@ class OdbliteSocketConnection {
2316
2331
  databaseHash;
2317
2332
  sql;
2318
2333
  beginPending = false;
2334
+ openEpoch;
2319
2335
  constructor(client, tenant, streamId){
2320
2336
  this.client = client;
2321
2337
  this.tenant = tenant;
@@ -2338,6 +2354,8 @@ class OdbliteSocketConnection {
2338
2354
  return res;
2339
2355
  }
2340
2356
  async streamExec(sql, params = []) {
2357
+ const opening = this.beginPending;
2358
+ if (!opening && !this.client.isConnectionLive(this.openEpoch)) throw new InteractiveTxConnectionLostError();
2341
2359
  const reqs = [];
2342
2360
  if (this.beginPending) {
2343
2361
  reqs.push({
@@ -2356,6 +2374,7 @@ class OdbliteSocketConnection {
2356
2374
  }
2357
2375
  });
2358
2376
  const results = await this.client.pipeline(this.tenant, reqs, this.streamId);
2377
+ if (opening) this.openEpoch = this.client.connectionEpoch;
2359
2378
  if (reqs.length > 1 && results[0]?.type === 'error') throw new Error(results[0].error.message);
2360
2379
  const res = results[results.length - 1];
2361
2380
  if (!res) throw new Error('odblite-socket: empty stream response');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pineliner/odb-client",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
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",