@pineliner/odb-client 1.4.0 → 1.5.1
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.map +1 -1
- package/dist/database/adapters/socket/client.d.ts +4 -0
- package/dist/database/adapters/socket/client.d.ts.map +1 -1
- package/dist/database/adapters/socket/protocol.d.ts.map +1 -1
- package/dist/index.cjs +37 -7
- package/dist/index.js +37 -7
- package/package.json +1 -1
- package/src/database/adapters/odblite-socket.ts +8 -3
- package/src/database/adapters/socket/client.ts +46 -4
- package/src/database/adapters/socket/protocol.ts +5 -0
|
@@ -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;
|
|
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"}
|
|
@@ -31,6 +31,10 @@ export declare class SocketClient {
|
|
|
31
31
|
* dropped connection FAILS its requests (the caller retries) instead of hanging. */
|
|
32
32
|
private failPending;
|
|
33
33
|
private connect;
|
|
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;
|
|
34
38
|
/** Allocate a fresh stream id for an interactive transaction (unique per connection). The server
|
|
35
39
|
* pins one SQLite connection per stream id, so all of a tx's statements run on it (read-your-own-
|
|
36
40
|
* writes). The id is carried in every frame of that tx via pipeline()'s `stream` arg. */
|
|
@@ -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;
|
|
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 +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
|
@@ -2235,6 +2235,7 @@ var __webpack_exports__ = {};
|
|
|
2235
2235
|
}
|
|
2236
2236
|
}
|
|
2237
2237
|
}
|
|
2238
|
+
const MAX_FRAME = 1024 * (Number(globalThis.process?.env?.ODB_MAX_FRAME_MB) || 64) * 1024;
|
|
2238
2239
|
class FrameReader {
|
|
2239
2240
|
buf = Buffer.alloc(0);
|
|
2240
2241
|
push(chunk) {
|
|
@@ -2245,6 +2246,7 @@ var __webpack_exports__ = {};
|
|
|
2245
2246
|
const out = [];
|
|
2246
2247
|
while(this.buf.length >= 4){
|
|
2247
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})`);
|
|
2248
2250
|
if (this.buf.length < 4 + len) break;
|
|
2249
2251
|
out.push(this.buf.subarray(4, 4 + len));
|
|
2250
2252
|
this.buf = this.buf.subarray(4 + len);
|
|
@@ -2253,6 +2255,9 @@ var __webpack_exports__ = {};
|
|
|
2253
2255
|
}
|
|
2254
2256
|
}
|
|
2255
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));
|
|
2256
2261
|
class SocketClient {
|
|
2257
2262
|
target;
|
|
2258
2263
|
socket;
|
|
@@ -2286,8 +2291,20 @@ var __webpack_exports__ = {};
|
|
|
2286
2291
|
this.socket = await Bun.connect({
|
|
2287
2292
|
...opts,
|
|
2288
2293
|
socket: {
|
|
2289
|
-
data: (
|
|
2290
|
-
|
|
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){
|
|
2291
2308
|
const msg = JSON.parse(frame.toString('utf8'));
|
|
2292
2309
|
const p = this.pending.get(msg.id);
|
|
2293
2310
|
if (p) {
|
|
@@ -2312,13 +2329,24 @@ var __webpack_exports__ = {};
|
|
|
2312
2329
|
});
|
|
2313
2330
|
this.writer = new FrameWriter(this.socket);
|
|
2314
2331
|
}
|
|
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
|
+
}
|
|
2315
2343
|
allocStream() {
|
|
2316
2344
|
return this.nextStreamId++;
|
|
2317
2345
|
}
|
|
2318
2346
|
async pipeline(tenant, requests, stream) {
|
|
2319
|
-
await this.ready;
|
|
2347
|
+
await this.ready.catch(()=>{});
|
|
2320
2348
|
if (!this.socket) {
|
|
2321
|
-
this.ready = this.
|
|
2349
|
+
this.ready = this.connectWithRetry();
|
|
2322
2350
|
this.ready.catch(()=>{});
|
|
2323
2351
|
await this.ready;
|
|
2324
2352
|
}
|
|
@@ -2424,13 +2452,15 @@ var __webpack_exports__ = {};
|
|
|
2424
2452
|
return toQueryResult(res);
|
|
2425
2453
|
}
|
|
2426
2454
|
async query(sql, params = []) {
|
|
2427
|
-
|
|
2455
|
+
const s = 'string' == typeof sql ? sql : sql.sql;
|
|
2456
|
+
const p = 'string' == typeof sql ? params : sql.args ?? [];
|
|
2457
|
+
if (null != this.streamId) return await this.streamExec(s, p);
|
|
2428
2458
|
return toQueryResult(await this.one([
|
|
2429
2459
|
{
|
|
2430
2460
|
type: 'execute',
|
|
2431
2461
|
stmt: {
|
|
2432
|
-
sql,
|
|
2433
|
-
args:
|
|
2462
|
+
sql: s,
|
|
2463
|
+
args: p.map(toHrana)
|
|
2434
2464
|
}
|
|
2435
2465
|
}
|
|
2436
2466
|
]));
|
package/dist/index.js
CHANGED
|
@@ -2145,6 +2145,7 @@ class FrameWriter {
|
|
|
2145
2145
|
}
|
|
2146
2146
|
}
|
|
2147
2147
|
}
|
|
2148
|
+
const MAX_FRAME = 1024 * (Number(globalThis.process?.env?.ODB_MAX_FRAME_MB) || 64) * 1024;
|
|
2148
2149
|
class FrameReader {
|
|
2149
2150
|
buf = Buffer.alloc(0);
|
|
2150
2151
|
push(chunk) {
|
|
@@ -2155,6 +2156,7 @@ class FrameReader {
|
|
|
2155
2156
|
const out = [];
|
|
2156
2157
|
while(this.buf.length >= 4){
|
|
2157
2158
|
const len = this.buf.readUInt32LE(0);
|
|
2159
|
+
if (len > MAX_FRAME) throw new Error(`odblite reply frame too large: ${len} bytes (max ${MAX_FRAME})`);
|
|
2158
2160
|
if (this.buf.length < 4 + len) break;
|
|
2159
2161
|
out.push(this.buf.subarray(4, 4 + len));
|
|
2160
2162
|
this.buf = this.buf.subarray(4 + len);
|
|
@@ -2163,6 +2165,9 @@ class FrameReader {
|
|
|
2163
2165
|
}
|
|
2164
2166
|
}
|
|
2165
2167
|
const REQUEST_TIMEOUT_MS = Number(globalThis.process?.env?.ODB_SOCKET_REQUEST_TIMEOUT_MS) || 30000;
|
|
2168
|
+
const RECONNECT_ATTEMPTS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_ATTEMPTS) || 6;
|
|
2169
|
+
const RECONNECT_BASE_MS = Number(globalThis.process?.env?.ODB_SOCKET_RECONNECT_BASE_MS) || 50;
|
|
2170
|
+
const sleep = (ms)=>new Promise((r)=>setTimeout(r, ms));
|
|
2166
2171
|
class SocketClient {
|
|
2167
2172
|
target;
|
|
2168
2173
|
socket;
|
|
@@ -2196,8 +2201,20 @@ class SocketClient {
|
|
|
2196
2201
|
this.socket = await Bun.connect({
|
|
2197
2202
|
...opts,
|
|
2198
2203
|
socket: {
|
|
2199
|
-
data: (
|
|
2200
|
-
|
|
2204
|
+
data: (s, chunk)=>{
|
|
2205
|
+
let frames;
|
|
2206
|
+
try {
|
|
2207
|
+
frames = this.reader.push(chunk);
|
|
2208
|
+
} catch (err) {
|
|
2209
|
+
this.socket = void 0;
|
|
2210
|
+
this.reader = new FrameReader();
|
|
2211
|
+
try {
|
|
2212
|
+
s.end?.();
|
|
2213
|
+
} catch {}
|
|
2214
|
+
this.failPending(err instanceof Error ? err : new Error(`odblite framing error: ${err}`));
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
for (const frame of frames){
|
|
2201
2218
|
const msg = JSON.parse(frame.toString('utf8'));
|
|
2202
2219
|
const p = this.pending.get(msg.id);
|
|
2203
2220
|
if (p) {
|
|
@@ -2222,13 +2239,24 @@ class SocketClient {
|
|
|
2222
2239
|
});
|
|
2223
2240
|
this.writer = new FrameWriter(this.socket);
|
|
2224
2241
|
}
|
|
2242
|
+
async connectWithRetry() {
|
|
2243
|
+
let lastErr;
|
|
2244
|
+
for(let attempt = 0; attempt < RECONNECT_ATTEMPTS; attempt++)try {
|
|
2245
|
+
await this.connect();
|
|
2246
|
+
return;
|
|
2247
|
+
} catch (err) {
|
|
2248
|
+
lastErr = err;
|
|
2249
|
+
if (attempt < RECONNECT_ATTEMPTS - 1) await sleep(RECONNECT_BASE_MS * 2 ** attempt);
|
|
2250
|
+
}
|
|
2251
|
+
throw lastErr instanceof Error ? lastErr : new Error(`odblite socket reconnect failed: ${lastErr}`);
|
|
2252
|
+
}
|
|
2225
2253
|
allocStream() {
|
|
2226
2254
|
return this.nextStreamId++;
|
|
2227
2255
|
}
|
|
2228
2256
|
async pipeline(tenant, requests, stream) {
|
|
2229
|
-
await this.ready;
|
|
2257
|
+
await this.ready.catch(()=>{});
|
|
2230
2258
|
if (!this.socket) {
|
|
2231
|
-
this.ready = this.
|
|
2259
|
+
this.ready = this.connectWithRetry();
|
|
2232
2260
|
this.ready.catch(()=>{});
|
|
2233
2261
|
await this.ready;
|
|
2234
2262
|
}
|
|
@@ -2334,13 +2362,15 @@ class OdbliteSocketConnection {
|
|
|
2334
2362
|
return toQueryResult(res);
|
|
2335
2363
|
}
|
|
2336
2364
|
async query(sql, params = []) {
|
|
2337
|
-
|
|
2365
|
+
const s = 'string' == typeof sql ? sql : sql.sql;
|
|
2366
|
+
const p = 'string' == typeof sql ? params : sql.args ?? [];
|
|
2367
|
+
if (null != this.streamId) return await this.streamExec(s, p);
|
|
2338
2368
|
return toQueryResult(await this.one([
|
|
2339
2369
|
{
|
|
2340
2370
|
type: 'execute',
|
|
2341
2371
|
stmt: {
|
|
2342
|
-
sql,
|
|
2343
|
-
args:
|
|
2372
|
+
sql: s,
|
|
2373
|
+
args: p.map(toHrana)
|
|
2344
2374
|
}
|
|
2345
2375
|
}
|
|
2346
2376
|
]));
|
package/package.json
CHANGED
|
@@ -91,9 +91,14 @@ class OdbliteSocketConnection implements Connection {
|
|
|
91
91
|
return toQueryResult(res)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
async query<T = any>(sql: string, params: any[] = []): Promise<QueryResult<T>> {
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
async query<T = any>(sql: string | { sql: string; args?: any[] }, params: any[] = []): Promise<QueryResult<T>> {
|
|
95
|
+
// Accept both query(sql, params) and query({ sql, args }) — symmetric with
|
|
96
|
+
// execute() and the other adapters. Without this, object-form calls sent the
|
|
97
|
+
// object itself as the SQL string ("Expected 'query' to be a string, got 'object'").
|
|
98
|
+
const s = typeof sql === 'string' ? sql : sql.sql
|
|
99
|
+
const p = typeof sql === 'string' ? params : (sql.args ?? [])
|
|
100
|
+
if (this.streamId != null) return (await this.streamExec(s, p)) as QueryResult<T>
|
|
101
|
+
return toQueryResult(await this.one([{ type: 'execute', stmt: { sql: s, args: p.map(toHrana) } }])) as QueryResult<T>
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
async execute(sql: string | { sql: string; args?: any[] }, params: any[] = []): Promise<QueryResult> {
|
|
@@ -24,6 +24,14 @@ export interface SocketTarget {
|
|
|
24
24
|
// hang forever. Generous by default; tune via ODB_SOCKET_REQUEST_TIMEOUT_MS.
|
|
25
25
|
const REQUEST_TIMEOUT_MS = Number((globalThis as any).process?.env?.ODB_SOCKET_REQUEST_TIMEOUT_MS) || 30000
|
|
26
26
|
|
|
27
|
+
// Auto-reconnect: when the socket is down (router restarting), retry the CONNECT with
|
|
28
|
+
// backoff so a request that lands during the restart window waits it out instead of
|
|
29
|
+
// failing — a redeploy/restart becomes transparent. Bounded so a truly-dead server
|
|
30
|
+
// still fails the request (caller retries / falls back to HTTP) rather than hanging.
|
|
31
|
+
const RECONNECT_ATTEMPTS = Number((globalThis as any).process?.env?.ODB_SOCKET_RECONNECT_ATTEMPTS) || 6
|
|
32
|
+
const RECONNECT_BASE_MS = Number((globalThis as any).process?.env?.ODB_SOCKET_RECONNECT_BASE_MS) || 50
|
|
33
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
34
|
+
|
|
27
35
|
interface Pending {
|
|
28
36
|
resolve: (r: StreamResult[]) => void
|
|
29
37
|
reject: (e: any) => void
|
|
@@ -65,8 +73,23 @@ export class SocketClient {
|
|
|
65
73
|
this.socket = await Bun.connect({
|
|
66
74
|
...opts,
|
|
67
75
|
socket: {
|
|
68
|
-
data: (
|
|
69
|
-
|
|
76
|
+
data: (s: any, chunk: Buffer) => {
|
|
77
|
+
// A framing error (oversized/corrupt length prefix from a buggy/hostile server) makes the byte
|
|
78
|
+
// stream unrecoverable — drop the connection and fail in-flight requests, exactly like a close.
|
|
79
|
+
// Without this the throw escapes the Bun `data` callback uncaught.
|
|
80
|
+
let frames: Buffer[]
|
|
81
|
+
try {
|
|
82
|
+
frames = this.reader.push(chunk)
|
|
83
|
+
} catch (err: any) {
|
|
84
|
+
this.socket = undefined
|
|
85
|
+
this.reader = new FrameReader()
|
|
86
|
+
try {
|
|
87
|
+
s.end?.()
|
|
88
|
+
} catch {}
|
|
89
|
+
this.failPending(err instanceof Error ? err : new Error(`odblite framing error: ${err}`))
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
for (const frame of frames) {
|
|
70
93
|
const msg = JSON.parse(frame.toString('utf8')) as { id: number; results: StreamResult[] }
|
|
71
94
|
const p = this.pending.get(msg.id)
|
|
72
95
|
if (p) {
|
|
@@ -95,6 +118,23 @@ export class SocketClient {
|
|
|
95
118
|
this.writer = new FrameWriter(this.socket)
|
|
96
119
|
}
|
|
97
120
|
|
|
121
|
+
/** Reconnect with bounded backoff — bridges a router restart so a request landing in the down-window
|
|
122
|
+
* waits it out instead of failing. After the last attempt the error propagates (caller retries / the
|
|
123
|
+
* auto-adapter can fall back to HTTP). Backoff: 50·100·200·400·800·1600ms ≈ 3s total by default. */
|
|
124
|
+
private async connectWithRetry(): Promise<void> {
|
|
125
|
+
let lastErr: any
|
|
126
|
+
for (let attempt = 0; attempt < RECONNECT_ATTEMPTS; attempt++) {
|
|
127
|
+
try {
|
|
128
|
+
await this.connect()
|
|
129
|
+
return
|
|
130
|
+
} catch (err) {
|
|
131
|
+
lastErr = err
|
|
132
|
+
if (attempt < RECONNECT_ATTEMPTS - 1) await sleep(RECONNECT_BASE_MS * 2 ** attempt)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
throw lastErr instanceof Error ? lastErr : new Error(`odblite socket reconnect failed: ${lastErr}`)
|
|
136
|
+
}
|
|
137
|
+
|
|
98
138
|
/** Allocate a fresh stream id for an interactive transaction (unique per connection). The server
|
|
99
139
|
* pins one SQLite connection per stream id, so all of a tx's statements run on it (read-your-own-
|
|
100
140
|
* writes). The id is carried in every frame of that tx via pipeline()'s `stream` arg. */
|
|
@@ -106,9 +146,11 @@ export class SocketClient {
|
|
|
106
146
|
// Await the in-flight connect FIRST — only reconnect if it's actually gone
|
|
107
147
|
// (closed/failed). Checking `!this.socket` before awaiting would start a
|
|
108
148
|
// second connect while the first is still resolving (a leaked connection).
|
|
109
|
-
await this.ready
|
|
149
|
+
await this.ready.catch(() => {}) // a prior failed connect must not poison this await; we re-check below
|
|
110
150
|
if (!this.socket) {
|
|
111
|
-
|
|
151
|
+
// Reconnect with backoff (bridges a restart). Shared promise so concurrent pipeline() callers
|
|
152
|
+
// ride ONE reconnect, not N. If it ultimately fails, every caller rejects (then retries / HTTP).
|
|
153
|
+
this.ready = this.connectWithRetry()
|
|
112
154
|
void this.ready.catch(() => {})
|
|
113
155
|
await this.ready
|
|
114
156
|
}
|
|
@@ -141,6 +141,10 @@ export class FrameWriter {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
// Reject an insane length prefix instead of buffering gigabytes for bytes that never arrive — a buggy or
|
|
145
|
+
// hostile server shouldn't be able to OOM the client inside the Bun `data` callback. Matches the router's
|
|
146
|
+
// frame cap; tune via ODB_MAX_FRAME_MB.
|
|
147
|
+
const MAX_FRAME = (Number((globalThis as any).process?.env?.ODB_MAX_FRAME_MB) || 64) * 1024 * 1024
|
|
144
148
|
export class FrameReader {
|
|
145
149
|
private buf: Buffer = Buffer.alloc(0)
|
|
146
150
|
push(chunk: Buffer): Buffer[] {
|
|
@@ -148,6 +152,7 @@ export class FrameReader {
|
|
|
148
152
|
const out: Buffer[] = []
|
|
149
153
|
while (this.buf.length >= 4) {
|
|
150
154
|
const len = this.buf.readUInt32LE(0)
|
|
155
|
+
if (len > MAX_FRAME) throw new Error(`odblite reply frame too large: ${len} bytes (max ${MAX_FRAME})`)
|
|
151
156
|
if (this.buf.length < 4 + len) break
|
|
152
157
|
out.push(this.buf.subarray(4, 4 + len))
|
|
153
158
|
this.buf = this.buf.subarray(4 + len)
|