@carllee1983/dbcli 1.25.0 → 1.28.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/cli.mjs CHANGED
@@ -25565,7 +25565,7 @@ class AuditLockManager {
25565
25565
  var LOCK_RETRY_BUDGET_MS = 200, LOCK_BACKOFF_START_MS = 5, LOCK_BACKOFF_MAX_MS = 50, STALE_LOCK_MULTIPLIER = 10;
25566
25566
  var init_lock = () => {};
25567
25567
 
25568
- // src/core/audit/rotation.ts
25568
+ // src/utils/jsonl-rotation.ts
25569
25569
  import { rename } from "fs/promises";
25570
25570
  function shouldRotate(stats, thresholds, nextLineByteLength) {
25571
25571
  const bytesAfter = stats.currentSizeBytes + nextLineByteLength;
@@ -25577,7 +25577,12 @@ async function rotate(currentPath, previousPath) {
25577
25577
  await rename(currentPath, previousPath);
25578
25578
  } catch {}
25579
25579
  }
25580
- var init_rotation = () => {};
25580
+ var init_jsonl_rotation = () => {};
25581
+
25582
+ // src/core/audit/rotation.ts
25583
+ var init_rotation = __esm(() => {
25584
+ init_jsonl_rotation();
25585
+ });
25581
25586
 
25582
25587
  // src/core/audit/logger.ts
25583
25588
  import { appendFile, mkdir as mkdir3, readFile, stat } from "fs/promises";
@@ -81217,7 +81222,7 @@ var {
81217
81222
  // package.json
81218
81223
  var package_default = {
81219
81224
  name: "@carllee1983/dbcli",
81220
- version: "1.25.0",
81225
+ version: "1.28.0",
81221
81226
  description: "Database CLI for AI agents",
81222
81227
  type: "module",
81223
81228
  publishConfig: {
@@ -81226,6 +81231,13 @@ var package_default = {
81226
81231
  bin: {
81227
81232
  dbcli: "./dist/cli.mjs"
81228
81233
  },
81234
+ exports: {
81235
+ ".": "./dist/cli.mjs",
81236
+ "./core": {
81237
+ types: "./dist/core.d.ts",
81238
+ import: "./dist/core.mjs"
81239
+ }
81240
+ },
81229
81241
  license: "MIT",
81230
81242
  author: "Carl Lee",
81231
81243
  repository: {
@@ -81296,9 +81308,11 @@ var package_default = {
81296
81308
  "@inquirer/prompts": "^8.4.3",
81297
81309
  "@testing-library/react": "^16.3.2",
81298
81310
  "@types/bun": "latest",
81311
+ "@types/pg": "^8.20.0",
81299
81312
  "@types/react": "^19.2.14",
81300
81313
  "@types/react-dom": "^19.2.3",
81301
81314
  autoprefixer: "^10.5.0",
81315
+ "dts-bundle-generator": "^9.5.1",
81302
81316
  eslint: "^10.4.0",
81303
81317
  "happy-dom": "^20.9.0",
81304
81318
  postcss: "^8.5.14",
@@ -94602,14 +94616,1214 @@ var useCommand = new Command("use").description("Switch or display the default d
94602
94616
  }
94603
94617
  });
94604
94618
 
94605
- // src/cli.ts
94619
+ // src/commands/proxy.ts
94606
94620
  init_config();
94621
+ init_validation();
94607
94622
  import { join as join32 } from "path";
94623
+
94624
+ // src/proxy/relay.ts
94625
+ class TcpRelay {
94626
+ clientBytes = 0;
94627
+ serverBytes = 0;
94628
+ opts;
94629
+ constructor(opts) {
94630
+ this.opts = opts;
94631
+ }
94632
+ fromClient(bytes) {
94633
+ this.opts.writeToUpstream(bytes);
94634
+ this.clientBytes += bytes.length;
94635
+ this.feed("client_to_server", bytes);
94636
+ }
94637
+ fromUpstream(bytes) {
94638
+ this.opts.writeToClient(bytes);
94639
+ this.serverBytes += bytes.length;
94640
+ this.feed("server_to_client", bytes);
94641
+ }
94642
+ feed(direction, bytes) {
94643
+ try {
94644
+ this.opts.analyzer.onData(direction, bytes);
94645
+ } catch (err) {
94646
+ this.opts.onSignal({
94647
+ kind: "parse_error",
94648
+ message: err instanceof Error ? err.message : String(err)
94649
+ });
94650
+ }
94651
+ }
94652
+ }
94653
+
94654
+ // src/proxy/events.ts
94655
+ init_jsonl_rotation();
94656
+ import { appendFile as appendFile2, mkdir as mkdir10, readFile as readFile6, stat as stat8 } from "fs/promises";
94657
+ import { dirname as dirname11 } from "path";
94658
+
94659
+ // src/proxy/sql-metadata.ts
94660
+ var KNOWN_KEYWORDS = [
94661
+ "SELECT",
94662
+ "INSERT",
94663
+ "UPDATE",
94664
+ "DELETE",
94665
+ "CREATE",
94666
+ "ALTER",
94667
+ "DROP",
94668
+ "TRUNCATE",
94669
+ "BEGIN",
94670
+ "COMMIT",
94671
+ "ROLLBACK",
94672
+ "SET",
94673
+ "SHOW",
94674
+ "USE"
94675
+ ];
94676
+ var KNOWN = new Set(KNOWN_KEYWORDS);
94677
+ function detectStatement(sql) {
94678
+ const m = sql.trim().match(/^([a-zA-Z]+)/);
94679
+ if (!m || !m[1])
94680
+ return "OTHER";
94681
+ const kw = m[1].toUpperCase();
94682
+ return KNOWN.has(kw) ? kw : "OTHER";
94683
+ }
94684
+ var TABLE_RE = /\b(?:FROM|JOIN|INTO|UPDATE)\s+["'`]?([A-Za-z_]\w*)["'`]?(?:\.["'`]?([A-Za-z_]\w*)["'`]?)?/gi;
94685
+ function extractTables(sql) {
94686
+ const seen = new Set;
94687
+ for (const m of sql.matchAll(TABLE_RE)) {
94688
+ const name2 = m[2] ?? m[1];
94689
+ if (name2)
94690
+ seen.add(name2);
94691
+ }
94692
+ return [...seen];
94693
+ }
94694
+ function redactLiterals(sql) {
94695
+ const noStrings = sql.replace(/'(?:[^']|'')*'/g, "?");
94696
+ return noStrings.replace(/\b\d+(?:\.\d+)?\b/g, "?");
94697
+ }
94698
+
94699
+ // src/proxy/events.ts
94700
+ var PROXY_EVENT_VERSION = 1;
94701
+ function hasSql(e) {
94702
+ return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
94703
+ }
94704
+ function applyRedaction(event, mode) {
94705
+ if (mode === "none" || !hasSql(event))
94706
+ return event;
94707
+ return { ...event, sql: redactLiterals(event.sql) };
94708
+ }
94709
+ var DEFAULT_ROTATION = {
94710
+ maxBytes: 50 * 1024 * 1024,
94711
+ maxEntries: 200000
94712
+ };
94713
+
94714
+ class EventWriter {
94715
+ path;
94716
+ previousPath;
94717
+ redact;
94718
+ maxBytes;
94719
+ maxEntries;
94720
+ dirEnsured = false;
94721
+ initialized = false;
94722
+ currentSizeBytes = 0;
94723
+ currentEntryCount = 0;
94724
+ writeChain = Promise.resolve();
94725
+ constructor(opts) {
94726
+ this.path = opts.path;
94727
+ this.previousPath = `${opts.path}.1`;
94728
+ this.redact = opts.redact;
94729
+ this.maxBytes = opts.rotation?.maxBytes ?? DEFAULT_ROTATION.maxBytes;
94730
+ this.maxEntries = opts.rotation?.maxEntries ?? DEFAULT_ROTATION.maxEntries;
94731
+ }
94732
+ write(event) {
94733
+ const op = this.writeChain.then(() => this.writeInternal(event));
94734
+ this.writeChain = op.then(() => {
94735
+ return;
94736
+ }, () => {
94737
+ return;
94738
+ });
94739
+ return op;
94740
+ }
94741
+ async writeInternal(event) {
94742
+ if (!this.dirEnsured) {
94743
+ await mkdir10(dirname11(this.path), { recursive: true });
94744
+ this.dirEnsured = true;
94745
+ }
94746
+ if (!this.initialized) {
94747
+ await this.syncCountersFromDisk();
94748
+ this.initialized = true;
94749
+ }
94750
+ const redacted = applyRedaction(event, this.redact);
94751
+ const line = JSON.stringify(redacted) + `
94752
+ `;
94753
+ const lineBytes = Buffer.byteLength(line, "utf8");
94754
+ if (shouldRotate({ currentSizeBytes: this.currentSizeBytes, currentEntryCount: this.currentEntryCount }, { maxBytes: this.maxBytes, maxEntries: this.maxEntries }, lineBytes)) {
94755
+ await rotate(this.path, this.previousPath);
94756
+ this.currentSizeBytes = 0;
94757
+ this.currentEntryCount = 0;
94758
+ }
94759
+ await appendFile2(this.path, line, { encoding: "utf8" });
94760
+ this.currentSizeBytes += lineBytes;
94761
+ this.currentEntryCount += 1;
94762
+ }
94763
+ async syncCountersFromDisk() {
94764
+ try {
94765
+ const s = await stat8(this.path);
94766
+ this.currentSizeBytes = s.size;
94767
+ const raw = await readFile6(this.path, "utf8");
94768
+ this.currentEntryCount = raw.split(`
94769
+ `).filter(Boolean).length;
94770
+ } catch {
94771
+ this.currentSizeBytes = 0;
94772
+ this.currentEntryCount = 0;
94773
+ }
94774
+ }
94775
+ }
94776
+
94777
+ // src/proxy/session.ts
94778
+ class ProxySession {
94779
+ o;
94780
+ active = null;
94781
+ queryCounter = 0;
94782
+ startedAt = 0;
94783
+ clientBytesAtBoundary = 0;
94784
+ pending = Promise.resolve();
94785
+ constructor(opts) {
94786
+ this.o = opts;
94787
+ }
94788
+ enqueue(event) {
94789
+ this.pending = this.pending.then(() => this.o.writeEvent(event)).catch((err) => {
94790
+ this.o.warn(`event write failed: ${err instanceof Error ? err.message : String(err)}`);
94791
+ });
94792
+ }
94793
+ async start() {
94794
+ this.startedAt = this.o.now();
94795
+ this.enqueue({
94796
+ version: PROXY_EVENT_VERSION,
94797
+ type: "session_started",
94798
+ timestamp: new Date().toISOString(),
94799
+ engine: this.o.engine,
94800
+ sessionId: this.o.sessionId,
94801
+ client: this.o.client,
94802
+ target: this.o.target
94803
+ });
94804
+ await this.flush();
94805
+ }
94806
+ onSignal(signal) {
94807
+ switch (signal.kind) {
94808
+ case "query":
94809
+ this.beginQuery(signal.sql, signal.tags ?? []);
94810
+ break;
94811
+ case "query_end":
94812
+ this.completeQuery(signal.rowCount ?? null);
94813
+ break;
94814
+ case "error":
94815
+ this.errorQuery(signal.code, signal.message);
94816
+ break;
94817
+ case "tag":
94818
+ if (this.active && !this.active.tags.includes(signal.tag)) {
94819
+ this.active.tags.push(signal.tag);
94820
+ }
94821
+ break;
94822
+ case "parse_error":
94823
+ this.enqueue({
94824
+ version: PROXY_EVENT_VERSION,
94825
+ type: "parse_error",
94826
+ timestamp: new Date().toISOString(),
94827
+ engine: this.o.engine,
94828
+ sessionId: this.o.sessionId,
94829
+ client: this.o.client,
94830
+ target: this.o.target,
94831
+ message: signal.message,
94832
+ tags: []
94833
+ });
94834
+ break;
94835
+ }
94836
+ }
94837
+ beginQuery(sql, tags) {
94838
+ const bytes = this.o.getBytes();
94839
+ this.queryCounter += 1;
94840
+ this.active = {
94841
+ queryId: `qry_${this.o.sessionId}_${this.queryCounter}`,
94842
+ sql,
94843
+ startedAt: this.o.now(),
94844
+ clientBytesAtStart: this.clientBytesAtBoundary,
94845
+ serverBytesAtStart: bytes.serverBytes,
94846
+ tags: [...tags]
94847
+ };
94848
+ this.enqueue({
94849
+ version: PROXY_EVENT_VERSION,
94850
+ type: "query_observed",
94851
+ timestamp: new Date().toISOString(),
94852
+ engine: this.o.engine,
94853
+ sessionId: this.o.sessionId,
94854
+ queryId: this.active.queryId,
94855
+ client: this.o.client,
94856
+ target: this.o.target,
94857
+ sql,
94858
+ statement: detectStatement(sql),
94859
+ tables: extractTables(sql),
94860
+ tags: [...this.active.tags]
94861
+ });
94862
+ }
94863
+ completeQuery(rowCount) {
94864
+ const q3 = this.active;
94865
+ if (!q3)
94866
+ return;
94867
+ const bytes = this.o.getBytes();
94868
+ const durationMs = this.o.now() - q3.startedAt;
94869
+ const requestBytes = bytes.clientBytes - q3.clientBytesAtStart;
94870
+ const responseBytes = bytes.serverBytes - q3.serverBytesAtStart;
94871
+ const slow = durationMs >= this.o.slowMs;
94872
+ this.enqueue({
94873
+ version: PROXY_EVENT_VERSION,
94874
+ type: "query_completed",
94875
+ timestamp: new Date().toISOString(),
94876
+ engine: this.o.engine,
94877
+ sessionId: this.o.sessionId,
94878
+ queryId: q3.queryId,
94879
+ client: this.o.client,
94880
+ target: this.o.target,
94881
+ sql: q3.sql,
94882
+ statement: detectStatement(q3.sql),
94883
+ tables: extractTables(q3.sql),
94884
+ durationMs,
94885
+ requestBytes,
94886
+ responseBytes,
94887
+ rowCount,
94888
+ slow,
94889
+ error: null,
94890
+ tags: [...q3.tags]
94891
+ });
94892
+ if (slow) {
94893
+ this.o.warn(`slow query (${durationMs}ms): ${q3.sql.slice(0, 80)}`);
94894
+ }
94895
+ this.clientBytesAtBoundary = bytes.clientBytes;
94896
+ this.active = null;
94897
+ }
94898
+ errorQuery(code, message) {
94899
+ const q3 = this.active;
94900
+ const bytes = this.o.getBytes();
94901
+ const startedAt = q3?.startedAt ?? this.o.now();
94902
+ this.enqueue({
94903
+ version: PROXY_EVENT_VERSION,
94904
+ type: "query_errored",
94905
+ timestamp: new Date().toISOString(),
94906
+ engine: this.o.engine,
94907
+ sessionId: this.o.sessionId,
94908
+ queryId: q3?.queryId ?? `qry_${this.o.sessionId}_err_${++this.queryCounter}`,
94909
+ client: this.o.client,
94910
+ target: this.o.target,
94911
+ sql: q3?.sql ?? "",
94912
+ statement: detectStatement(q3?.sql ?? ""),
94913
+ tables: extractTables(q3?.sql ?? ""),
94914
+ durationMs: this.o.now() - startedAt,
94915
+ requestBytes: q3 ? bytes.clientBytes - q3.clientBytesAtStart : 0,
94916
+ responseBytes: q3 ? bytes.serverBytes - q3.serverBytesAtStart : 0,
94917
+ rowCount: null,
94918
+ error: { code, message },
94919
+ tags: q3 ? [...q3.tags] : []
94920
+ });
94921
+ this.clientBytesAtBoundary = bytes.clientBytes;
94922
+ this.active = null;
94923
+ }
94924
+ async end(reason) {
94925
+ const bytes = this.o.getBytes();
94926
+ this.enqueue({
94927
+ version: PROXY_EVENT_VERSION,
94928
+ type: "session_ended",
94929
+ timestamp: new Date().toISOString(),
94930
+ engine: this.o.engine,
94931
+ sessionId: this.o.sessionId,
94932
+ client: this.o.client,
94933
+ target: this.o.target,
94934
+ durationMs: this.o.now() - this.startedAt,
94935
+ requestBytes: bytes.clientBytes,
94936
+ responseBytes: bytes.serverBytes,
94937
+ reason
94938
+ });
94939
+ await this.flush();
94940
+ }
94941
+ async flush() {
94942
+ await this.pending;
94943
+ }
94944
+ }
94945
+
94946
+ // src/proxy/analyzers/types.ts
94947
+ var UTF8 = new TextDecoder;
94948
+
94949
+ class FrameBuffer {
94950
+ buf = new Uint8Array(0);
94951
+ get length() {
94952
+ return this.buf.length;
94953
+ }
94954
+ push(chunk) {
94955
+ if (this.buf.length === 0) {
94956
+ this.buf = chunk.slice();
94957
+ return;
94958
+ }
94959
+ const next = new Uint8Array(this.buf.length + chunk.length);
94960
+ next.set(this.buf, 0);
94961
+ next.set(chunk, this.buf.length);
94962
+ this.buf = next;
94963
+ }
94964
+ peek(n) {
94965
+ return this.buf.subarray(0, Math.min(n, this.buf.length));
94966
+ }
94967
+ byteAt(offset) {
94968
+ return this.buf[offset];
94969
+ }
94970
+ consume(n) {
94971
+ this.buf = this.buf.subarray(Math.min(n, this.buf.length));
94972
+ }
94973
+ readUInt24LE(offset) {
94974
+ const b0 = this.buf[offset] ?? 0;
94975
+ const b1 = this.buf[offset + 1] ?? 0;
94976
+ const b2 = this.buf[offset + 2] ?? 0;
94977
+ return b0 | b1 << 8 | b2 << 16;
94978
+ }
94979
+ readUInt16LE(offset) {
94980
+ const b0 = this.buf[offset] ?? 0;
94981
+ const b1 = this.buf[offset + 1] ?? 0;
94982
+ return b0 | b1 << 8;
94983
+ }
94984
+ readUInt32BE(offset) {
94985
+ const b0 = this.buf[offset] ?? 0;
94986
+ const b1 = this.buf[offset + 1] ?? 0;
94987
+ const b2 = this.buf[offset + 2] ?? 0;
94988
+ const b3 = this.buf[offset + 3] ?? 0;
94989
+ return (b0 << 24 >>> 0) + (b1 << 16) + (b2 << 8) + b3;
94990
+ }
94991
+ text(start, end) {
94992
+ return UTF8.decode(this.buf.subarray(start, end));
94993
+ }
94994
+ }
94995
+
94996
+ // src/proxy/analyzers/mysql.ts
94997
+ var COM_QUERY = 3;
94998
+ var COM_STMT_PREPARE = 22;
94999
+ var COM_STMT_EXECUTE = 23;
95000
+ var HEADER = 4;
95001
+ function createMysqlAnalyzer(deps) {
95002
+ const clientBuf = new FrameBuffer;
95003
+ const serverBuf = new FrameBuffer;
95004
+ let awaitingResponse = false;
95005
+ function handleClientPacket(payloadStart, payloadLen) {
95006
+ const seqId = clientBuf.byteAt(payloadStart - 1);
95007
+ if (seqId !== 0)
95008
+ return;
95009
+ const cmd = clientBuf.byteAt(payloadStart);
95010
+ if (cmd === undefined)
95011
+ return;
95012
+ if (cmd === COM_QUERY) {
95013
+ const sql = clientBuf.text(payloadStart + 1, payloadStart + payloadLen);
95014
+ deps.emit({ kind: "query", sql });
95015
+ awaitingResponse = true;
95016
+ } else if (cmd === COM_STMT_PREPARE) {
95017
+ const sql = clientBuf.text(payloadStart + 1, payloadStart + payloadLen);
95018
+ deps.emit({ kind: "query", sql, tags: ["prepared_statement"] });
95019
+ awaitingResponse = true;
95020
+ } else if (cmd === COM_STMT_EXECUTE) {
95021
+ deps.emit({ kind: "tag", tag: "prepared_statement" });
95022
+ awaitingResponse = true;
95023
+ }
95024
+ }
95025
+ function handleServerPacket(payloadStart, payloadLen) {
95026
+ if (!awaitingResponse)
95027
+ return;
95028
+ const first = serverBuf.byteAt(payloadStart);
95029
+ if (first === undefined)
95030
+ return;
95031
+ if (first === 255) {
95032
+ const code = serverBuf.readUInt16LE(payloadStart + 1);
95033
+ let msgStart = payloadStart + 3;
95034
+ if (serverBuf.byteAt(msgStart) === 35) {
95035
+ msgStart += 6;
95036
+ }
95037
+ const message = serverBuf.text(msgStart, payloadStart + payloadLen);
95038
+ deps.emit({ kind: "error", code: String(code), message });
95039
+ awaitingResponse = false;
95040
+ } else if (first === 0) {
95041
+ deps.emit({ kind: "query_end", rowCount: null });
95042
+ awaitingResponse = false;
95043
+ } else {
95044
+ deps.emit({ kind: "tag", tag: "parse_partial" });
95045
+ deps.emit({ kind: "query_end", rowCount: null });
95046
+ awaitingResponse = false;
95047
+ }
95048
+ }
95049
+ function drain(buf, onPacket) {
95050
+ while (buf.length >= HEADER) {
95051
+ const payloadLen = buf.readUInt24LE(0);
95052
+ if (buf.length < HEADER + payloadLen)
95053
+ break;
95054
+ onPacket(HEADER, payloadLen);
95055
+ buf.consume(HEADER + payloadLen);
95056
+ }
95057
+ }
95058
+ return {
95059
+ onData(direction, chunk) {
95060
+ try {
95061
+ if (direction === "client_to_server") {
95062
+ clientBuf.push(chunk);
95063
+ drain(clientBuf, handleClientPacket);
95064
+ } else {
95065
+ serverBuf.push(chunk);
95066
+ drain(serverBuf, handleServerPacket);
95067
+ }
95068
+ } catch (err) {
95069
+ deps.emit({
95070
+ kind: "parse_error",
95071
+ message: err instanceof Error ? err.message : String(err)
95072
+ });
95073
+ }
95074
+ }
95075
+ };
95076
+ }
95077
+
95078
+ // src/proxy/analyzers/postgresql.ts
95079
+ function rowCountFromTag(tag) {
95080
+ const parts = tag.trim().split(/\s+/);
95081
+ const last = parts[parts.length - 1];
95082
+ if (last === undefined)
95083
+ return null;
95084
+ const n = Number(last);
95085
+ return Number.isInteger(n) ? n : null;
95086
+ }
95087
+ var MAX_STARTUP_LEN = 1e4;
95088
+ function createPostgresAnalyzer(deps) {
95089
+ const clientBuf = new FrameBuffer;
95090
+ const serverBuf = new FrameBuffer;
95091
+ let startupSeen = false;
95092
+ let awaitingResponse = false;
95093
+ function cStringEnd(buf, start, end) {
95094
+ let i = start;
95095
+ while (i < end && buf.byteAt(i) !== 0)
95096
+ i++;
95097
+ return i;
95098
+ }
95099
+ function readCString(buf, start, end) {
95100
+ return buf.text(start, cStringEnd(buf, start, end));
95101
+ }
95102
+ function handleClientMessage(type, bodyStart, bodyEnd) {
95103
+ const t2 = String.fromCharCode(type);
95104
+ if (t2 === "Q") {
95105
+ const sql = readCString(clientBuf, bodyStart, bodyEnd);
95106
+ deps.emit({ kind: "query", sql });
95107
+ awaitingResponse = true;
95108
+ } else if (t2 === "P" || t2 === "B" || t2 === "E" || t2 === "S" || t2 === "D") {
95109
+ deps.emit({ kind: "tag", tag: "extended_protocol" });
95110
+ if (t2 === "P") {
95111
+ const nameEnd = cStringEnd(clientBuf, bodyStart, bodyEnd) + 1;
95112
+ const sql = readCString(clientBuf, nameEnd, bodyEnd);
95113
+ if (sql)
95114
+ deps.emit({ kind: "query", sql, tags: ["extended_protocol"] });
95115
+ awaitingResponse = true;
95116
+ }
95117
+ }
95118
+ }
95119
+ function handleServerMessage(type, bodyStart, bodyEnd) {
95120
+ if (!awaitingResponse)
95121
+ return;
95122
+ const t2 = String.fromCharCode(type);
95123
+ if (t2 === "E") {
95124
+ let code = null;
95125
+ let message = "";
95126
+ let i = bodyStart;
95127
+ while (i < bodyEnd) {
95128
+ const fieldType = serverBuf.byteAt(i);
95129
+ if (fieldType === undefined || fieldType === 0)
95130
+ break;
95131
+ i += 1;
95132
+ const valEnd = cStringEnd(serverBuf, i, bodyEnd);
95133
+ const value = serverBuf.text(i, valEnd);
95134
+ i = valEnd + 1;
95135
+ if (fieldType === 67)
95136
+ code = value;
95137
+ else if (fieldType === 77)
95138
+ message = value;
95139
+ }
95140
+ deps.emit({ kind: "error", code, message });
95141
+ awaitingResponse = false;
95142
+ } else if (t2 === "C") {
95143
+ const tag = readCString(serverBuf, bodyStart, bodyEnd);
95144
+ deps.emit({ kind: "query_end", rowCount: rowCountFromTag(tag) });
95145
+ awaitingResponse = false;
95146
+ }
95147
+ }
95148
+ function drain(buf, isClient, onMessage) {
95149
+ if (isClient && !startupSeen) {
95150
+ if (buf.length < 4)
95151
+ return;
95152
+ const len = buf.readUInt32BE(0);
95153
+ if (len >= 4 && len <= buf.length && len <= MAX_STARTUP_LEN) {
95154
+ startupSeen = true;
95155
+ buf.consume(len);
95156
+ } else {
95157
+ startupSeen = true;
95158
+ }
95159
+ }
95160
+ while (buf.length >= 5) {
95161
+ const type = buf.byteAt(0);
95162
+ const len = buf.readUInt32BE(1);
95163
+ const total = 1 + len;
95164
+ if (buf.length < total)
95165
+ break;
95166
+ onMessage(type, 5, total);
95167
+ buf.consume(total);
95168
+ }
95169
+ }
95170
+ return {
95171
+ onData(direction, chunk) {
95172
+ try {
95173
+ if (direction === "client_to_server") {
95174
+ clientBuf.push(chunk);
95175
+ drain(clientBuf, true, handleClientMessage);
95176
+ } else {
95177
+ serverBuf.push(chunk);
95178
+ drain(serverBuf, false, handleServerMessage);
95179
+ }
95180
+ } catch (err) {
95181
+ deps.emit({
95182
+ kind: "parse_error",
95183
+ message: err instanceof Error ? err.message : String(err)
95184
+ });
95185
+ }
95186
+ }
95187
+ };
95188
+ }
95189
+
95190
+ // src/proxy/server.ts
95191
+ function makeAnalyzer(engine, deps) {
95192
+ return engine === "postgresql" ? createPostgresAnalyzer(deps) : createMysqlAnalyzer(deps);
95193
+ }
95194
+
95195
+ class ProxyServer {
95196
+ o;
95197
+ writer;
95198
+ listener = null;
95199
+ sessionCounter = 0;
95200
+ writeFailed = false;
95201
+ constructor(opts) {
95202
+ this.o = opts;
95203
+ this.writer = new EventWriter({ path: opts.eventsPath, redact: opts.redact });
95204
+ }
95205
+ get port() {
95206
+ return this.listener?.port ?? null;
95207
+ }
95208
+ async start() {
95209
+ await this.writer.write({
95210
+ version: PROXY_EVENT_VERSION,
95211
+ type: "proxy_started",
95212
+ timestamp: new Date().toISOString(),
95213
+ engine: this.o.engine,
95214
+ sessionId: "pxy_root",
95215
+ listen: `${this.o.listen.host}:${this.o.listen.port}`,
95216
+ target: `${this.o.target.host}:${this.o.target.port}`
95217
+ });
95218
+ this.listener = Bun.listen({
95219
+ hostname: this.o.listen.host,
95220
+ port: this.o.listen.port,
95221
+ socket: {
95222
+ open: (client) => {
95223
+ this.handleConnection(client);
95224
+ },
95225
+ data(client, chunk) {
95226
+ const ctx = client.data;
95227
+ ctx?.onData?.(chunk);
95228
+ },
95229
+ close(client) {
95230
+ const ctx = client.data;
95231
+ ctx?.onClose?.();
95232
+ },
95233
+ error(client) {
95234
+ const ctx = client.data;
95235
+ ctx?.onClose?.();
95236
+ }
95237
+ }
95238
+ });
95239
+ }
95240
+ stop() {
95241
+ this.listener?.stop();
95242
+ this.listener = null;
95243
+ }
95244
+ async handleConnection(client) {
95245
+ if (this.writeFailed) {
95246
+ client.end();
95247
+ return;
95248
+ }
95249
+ this.sessionCounter += 1;
95250
+ const sessionId = `pxy_${this.sessionCounter}`;
95251
+ const clientAddr = client.remoteAddress ?? "unknown";
95252
+ const target = `${this.o.target.host}:${this.o.target.port}`;
95253
+ const earlyBuffer = [];
95254
+ let relay = null;
95255
+ client.data = {
95256
+ onData: (chunk) => {
95257
+ if (relay) {
95258
+ relay.fromClient(chunk);
95259
+ } else {
95260
+ earlyBuffer.push(chunk);
95261
+ }
95262
+ },
95263
+ onClose: () => {
95264
+ client.end();
95265
+ }
95266
+ };
95267
+ const session = new ProxySession({
95268
+ sessionId,
95269
+ engine: this.o.engine,
95270
+ client: clientAddr,
95271
+ target,
95272
+ slowMs: this.o.slowMs,
95273
+ now: () => performance.now(),
95274
+ getBytes: () => ({
95275
+ clientBytes: relay?.clientBytes ?? 0,
95276
+ serverBytes: relay?.serverBytes ?? 0
95277
+ }),
95278
+ writeEvent: (e) => this.writer.write(e).catch((err) => {
95279
+ this.writeFailed = true;
95280
+ this.o.warn(`event write failed: ${err instanceof Error ? err.message : String(err)}`);
95281
+ }),
95282
+ warn: this.o.warn
95283
+ });
95284
+ let upstream;
95285
+ try {
95286
+ const analyzer = makeAnalyzer(this.o.engine, { emit: (s) => session.onSignal(s) });
95287
+ upstream = await Bun.connect({
95288
+ hostname: this.o.target.host,
95289
+ port: this.o.target.port,
95290
+ socket: {
95291
+ data(_s, chunk) {
95292
+ relay?.fromUpstream(chunk);
95293
+ },
95294
+ close() {
95295
+ session.end("upstream_closed").then(() => client.end());
95296
+ },
95297
+ error() {
95298
+ session.end("error").then(() => client.end());
95299
+ }
95300
+ }
95301
+ });
95302
+ relay = new TcpRelay({
95303
+ writeToClient: (b) => {
95304
+ client.write(b);
95305
+ },
95306
+ writeToUpstream: (b) => {
95307
+ upstream.write(b);
95308
+ },
95309
+ analyzer,
95310
+ onSignal: (s) => session.onSignal(s)
95311
+ });
95312
+ const liveUpstream = upstream;
95313
+ client.data = {
95314
+ onData: (chunk) => relay?.fromClient(chunk),
95315
+ onClose: () => void session.end("client_closed").then(() => liveUpstream.end())
95316
+ };
95317
+ for (const chunk of earlyBuffer) {
95318
+ relay.fromClient(chunk);
95319
+ }
95320
+ earlyBuffer.length = 0;
95321
+ } catch (err) {
95322
+ this.o.warn(`upstream connect failed: ${err instanceof Error ? err.message : String(err)}`);
95323
+ await session.start();
95324
+ await session.end("error");
95325
+ client.end();
95326
+ return;
95327
+ }
95328
+ try {
95329
+ await session.start();
95330
+ } catch (err) {
95331
+ this.o.warn(`session start failed: ${err instanceof Error ? err.message : String(err)}`);
95332
+ client.end();
95333
+ upstream.end();
95334
+ return;
95335
+ }
95336
+ }
95337
+ }
95338
+
95339
+ // src/proxy/event-reader.ts
95340
+ import { readFile as readFile7 } from "fs/promises";
95341
+ async function readEvents(path6, opts) {
95342
+ const candidates = opts.includeRotated ? [path6, `${path6}.1`] : [path6];
95343
+ const files = [];
95344
+ const events = [];
95345
+ let malformedLines = 0;
95346
+ for (const file of candidates) {
95347
+ let raw;
95348
+ try {
95349
+ raw = await readFile7(file, "utf8");
95350
+ } catch (err) {
95351
+ if (err.code !== "ENOENT")
95352
+ throw err;
95353
+ continue;
95354
+ }
95355
+ files.push(file);
95356
+ for (const rawLine of raw.split(`
95357
+ `)) {
95358
+ const trimmed = rawLine.trim();
95359
+ if (!trimmed)
95360
+ continue;
95361
+ try {
95362
+ events.push(JSON.parse(trimmed));
95363
+ } catch {
95364
+ malformedLines += 1;
95365
+ }
95366
+ }
95367
+ }
95368
+ events.sort((a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0);
95369
+ return { events, malformedLines, files };
95370
+ }
95371
+
95372
+ // src/proxy/analyze.ts
95373
+ var isCompleted = (e) => e.type === "query_completed";
95374
+ var isErrored = (e) => e.type === "query_errored";
95375
+ function percentile(values, p) {
95376
+ if (values.length === 0)
95377
+ return 0;
95378
+ const sorted = [...values].sort((a, b) => a - b);
95379
+ const rank = Math.ceil(p / 100 * sorted.length);
95380
+ const idx = Math.min(Math.max(rank, 1), sorted.length) - 1;
95381
+ return sorted[idx];
95382
+ }
95383
+ function fingerprintSql(sql) {
95384
+ return redactLiterals(sql).replace(/\s+/g, " ").trim();
95385
+ }
95386
+ function shellEscapeDq(s) {
95387
+ return s.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/`/g, "\\`").replace(/"/g, "\\\"");
95388
+ }
95389
+ function buildByFingerprint(events, slowMs, top) {
95390
+ const errorByFp = new Map;
95391
+ for (const e of events.filter(isErrored)) {
95392
+ const fp = fingerprintSql(e.sql);
95393
+ errorByFp.set(fp, (errorByFp.get(fp) ?? 0) + 1);
95394
+ }
95395
+ const groups = new Map;
95396
+ for (const e of events.filter(isCompleted)) {
95397
+ const fp = fingerprintSql(e.sql);
95398
+ let g = groups.get(fp);
95399
+ if (!g) {
95400
+ g = {
95401
+ fingerprint: fp,
95402
+ statement: e.statement,
95403
+ tables: e.tables,
95404
+ durations: [],
95405
+ reqBytes: 0,
95406
+ respBytes: 0,
95407
+ rows: [],
95408
+ slowCount: 0,
95409
+ exampleSql: e.sql,
95410
+ exampleQueryId: e.queryId,
95411
+ exampleDuration: e.durationMs
95412
+ };
95413
+ groups.set(fp, g);
95414
+ }
95415
+ g.durations.push(e.durationMs);
95416
+ g.reqBytes += e.requestBytes;
95417
+ g.respBytes += e.responseBytes;
95418
+ if (e.rowCount !== null)
95419
+ g.rows.push(e.rowCount);
95420
+ if (e.durationMs >= slowMs)
95421
+ g.slowCount += 1;
95422
+ if (e.durationMs > g.exampleDuration) {
95423
+ g.exampleDuration = e.durationMs;
95424
+ g.exampleSql = e.sql;
95425
+ g.exampleQueryId = e.queryId;
95426
+ }
95427
+ }
95428
+ const stats = [...groups.values()].map((g) => {
95429
+ const count = g.durations.length;
95430
+ const total = g.durations.reduce((sum, d) => sum + d, 0);
95431
+ return {
95432
+ fingerprint: g.fingerprint,
95433
+ statement: g.statement,
95434
+ tables: g.tables,
95435
+ count,
95436
+ durationMs: {
95437
+ total,
95438
+ avg: count ? Math.round(total / count) : 0,
95439
+ p95: percentile(g.durations, 95),
95440
+ max: count ? Math.max(...g.durations) : 0
95441
+ },
95442
+ rowsAvg: g.rows.length ? Math.round(g.rows.reduce((sum, r) => sum + r, 0) / g.rows.length) : 0,
95443
+ bytesAvg: {
95444
+ request: count ? Math.round(g.reqBytes / count) : 0,
95445
+ response: count ? Math.round(g.respBytes / count) : 0
95446
+ },
95447
+ errorCount: errorByFp.get(g.fingerprint) ?? 0,
95448
+ slowCount: g.slowCount,
95449
+ redacted: redactLiterals(g.exampleSql) === g.exampleSql,
95450
+ exampleSql: g.exampleSql,
95451
+ exampleQueryId: g.exampleQueryId
95452
+ };
95453
+ });
95454
+ stats.sort((a, b) => b.durationMs.total - a.durationMs.total);
95455
+ return stats.map((s, i) => {
95456
+ if (i < top && s.statement === "SELECT") {
95457
+ const sql = shellEscapeDq(s.exampleSql);
95458
+ return {
95459
+ ...s,
95460
+ suggestedCommands: [`dbcli explain "${sql}"`, `dbcli guide missing-index-for "${sql}"`]
95461
+ };
95462
+ }
95463
+ return s;
95464
+ });
95465
+ }
95466
+ function buildSlowest(events, top) {
95467
+ return events.filter(isCompleted).sort((a, b) => b.durationMs - a.durationMs).slice(0, top).map((e) => ({
95468
+ queryId: e.queryId,
95469
+ durationMs: e.durationMs,
95470
+ sql: e.sql,
95471
+ statement: e.statement,
95472
+ tables: e.tables,
95473
+ timestamp: e.timestamp,
95474
+ sessionId: e.sessionId
95475
+ }));
95476
+ }
95477
+ function buildErrors(events) {
95478
+ const groups = new Map;
95479
+ for (const e of events.filter(isErrored)) {
95480
+ const key = `${e.error.code ?? ""} ${e.error.message}`;
95481
+ let g = groups.get(key);
95482
+ if (!g) {
95483
+ g = {
95484
+ code: e.error.code,
95485
+ message: e.error.message,
95486
+ count: 0,
95487
+ fingerprint: fingerprintSql(e.sql),
95488
+ exampleSql: e.sql
95489
+ };
95490
+ groups.set(key, g);
95491
+ }
95492
+ g.count += 1;
95493
+ }
95494
+ return [...groups.values()].sort((a, b) => b.count - a.count);
95495
+ }
95496
+ function buildHotTables(events) {
95497
+ const map = new Map;
95498
+ for (const e of events.filter(isCompleted)) {
95499
+ for (const t2 of e.tables) {
95500
+ let g = map.get(t2);
95501
+ if (!g) {
95502
+ g = { queryCount: 0, totalDurationMs: 0 };
95503
+ map.set(t2, g);
95504
+ }
95505
+ g.queryCount += 1;
95506
+ g.totalDurationMs += e.durationMs;
95507
+ }
95508
+ }
95509
+ return [...map.entries()].map(([table, g]) => ({ table, queryCount: g.queryCount, totalDurationMs: g.totalDurationMs })).sort((a, b) => b.queryCount - a.queryCount);
95510
+ }
95511
+ function buildRepetition(events, threshold) {
95512
+ const groups = new Map;
95513
+ for (const e of events.filter(isCompleted)) {
95514
+ const fp = fingerprintSql(e.sql);
95515
+ const key = `${e.sessionId} ${fp}`;
95516
+ const ts = Date.parse(e.timestamp);
95517
+ let g = groups.get(key);
95518
+ if (!g) {
95519
+ g = {
95520
+ fingerprint: fp,
95521
+ sessionId: e.sessionId,
95522
+ tables: e.tables,
95523
+ count: 0,
95524
+ totalDurationMs: 0,
95525
+ minTs: ts,
95526
+ maxTs: ts
95527
+ };
95528
+ groups.set(key, g);
95529
+ }
95530
+ g.count += 1;
95531
+ g.totalDurationMs += e.durationMs;
95532
+ if (ts < g.minTs)
95533
+ g.minTs = ts;
95534
+ if (ts > g.maxTs)
95535
+ g.maxTs = ts;
95536
+ }
95537
+ return [...groups.values()].filter((g) => g.count >= threshold).map((g) => ({
95538
+ fingerprint: g.fingerprint,
95539
+ sessionId: g.sessionId,
95540
+ count: g.count,
95541
+ spanMs: g.maxTs - g.minTs,
95542
+ totalDurationMs: g.totalDurationMs,
95543
+ tables: g.tables
95544
+ })).sort((a, b) => b.count - a.count);
95545
+ }
95546
+ function analyzeEvents(events, opts) {
95547
+ const timestamps = events.map((e) => e.timestamp).filter(Boolean).sort();
95548
+ const from = timestamps[0] ?? null;
95549
+ const to = timestamps[timestamps.length - 1] ?? null;
95550
+ return {
95551
+ version: 1,
95552
+ tool: "proxy-analyze",
95553
+ engine: events[0]?.engine ?? null,
95554
+ source: {
95555
+ files: opts.sourceFiles,
95556
+ eventsRead: events.length,
95557
+ malformedLines: opts.malformedLines,
95558
+ timeSpan: {
95559
+ from,
95560
+ to,
95561
+ durationMs: from && to ? Date.parse(to) - Date.parse(from) : 0
95562
+ }
95563
+ },
95564
+ summary: buildSummary(events, opts.slowMs),
95565
+ byFingerprint: buildByFingerprint(events, opts.slowMs, opts.top),
95566
+ slowest: buildSlowest(events, opts.top),
95567
+ errors: buildErrors(events),
95568
+ hotTables: buildHotTables(events),
95569
+ repetition: buildRepetition(events, opts.nPlusOne)
95570
+ };
95571
+ }
95572
+ function buildSummary(events, slowMs) {
95573
+ const completed = events.filter(isCompleted);
95574
+ const errored = events.filter(isErrored);
95575
+ const durations = completed.map((e) => e.durationMs);
95576
+ const queries = completed.length;
95577
+ const errors3 = errored.length;
95578
+ const denom = queries + errors3;
95579
+ return {
95580
+ sessions: new Set(events.filter((e) => e.type === "session_started").map((e) => e.sessionId)).size,
95581
+ queries,
95582
+ errors: errors3,
95583
+ errorRate: denom === 0 ? 0 : errors3 / denom,
95584
+ parseErrors: events.filter((e) => e.type === "parse_error").length,
95585
+ slowCount: completed.filter((e) => e.durationMs >= slowMs).length,
95586
+ latencyMs: {
95587
+ p50: percentile(durations, 50),
95588
+ p95: percentile(durations, 95),
95589
+ p99: percentile(durations, 99),
95590
+ max: durations.length ? Math.max(...durations) : 0
95591
+ },
95592
+ bytes: {
95593
+ request: completed.reduce((sum, e) => sum + e.requestBytes, 0),
95594
+ response: completed.reduce((sum, e) => sum + e.responseBytes, 0)
95595
+ }
95596
+ };
95597
+ }
95598
+
95599
+ // src/proxy/analyze-render.ts
95600
+ function renderAnalysisText(report, top) {
95601
+ if (report.summary.queries === 0 && report.summary.errors === 0) {
95602
+ return "no events to analyze";
95603
+ }
95604
+ const s = report.summary;
95605
+ const L2 = [];
95606
+ L2.push("SUMMARY");
95607
+ L2.push(` engine: ${report.engine ?? "unknown"} sessions: ${s.sessions} ` + `queries: ${s.queries} errors: ${s.errors} (${(s.errorRate * 100).toFixed(2)}%)`);
95608
+ L2.push(` latency ms: p50=${s.latencyMs.p50} p95=${s.latencyMs.p95} ` + `p99=${s.latencyMs.p99} max=${s.latencyMs.max} slow=${s.slowCount}`);
95609
+ L2.push(` bytes: req=${s.bytes.request} resp=${s.bytes.response}`);
95610
+ L2.push("", "TOP QUERIES BY TOTAL TIME");
95611
+ for (const f of report.byFingerprint.slice(0, top)) {
95612
+ L2.push(` [${f.count}x total=${f.durationMs.total}ms avg=${f.durationMs.avg} ` + `p95=${f.durationMs.p95}] ${f.fingerprint}`);
95613
+ }
95614
+ L2.push("", "SLOWEST SINGLE QUERIES");
95615
+ for (const q3 of report.slowest.slice(0, top)) {
95616
+ L2.push(` ${q3.durationMs}ms ${q3.sql}`);
95617
+ }
95618
+ L2.push("", "HOT TABLES");
95619
+ for (const t2 of report.hotTables.slice(0, top)) {
95620
+ L2.push(` ${t2.queryCount}x ${t2.totalDurationMs}ms ${t2.table}`);
95621
+ }
95622
+ L2.push("", "ERRORS");
95623
+ if (report.errors.length === 0)
95624
+ L2.push(" (none)");
95625
+ for (const e of report.errors.slice(0, top)) {
95626
+ L2.push(` ${e.count}x [${e.code ?? "?"}] ${e.message}`);
95627
+ }
95628
+ L2.push("", "N+1 SUSPECTS");
95629
+ if (report.repetition.length === 0)
95630
+ L2.push(" (none)");
95631
+ for (const r of report.repetition.slice(0, top)) {
95632
+ L2.push(` ${r.count}x in session ${r.sessionId} (${r.spanMs}ms) ${r.fingerprint}`);
95633
+ }
95634
+ const cmds = [...new Set(report.byFingerprint.flatMap((f) => f.suggestedCommands ?? []))];
95635
+ if (cmds.length) {
95636
+ L2.push("", "SUGGESTED COMMANDS");
95637
+ for (const c2 of cmds)
95638
+ L2.push(` ${c2}`);
95639
+ }
95640
+ return L2.join(`
95641
+ `);
95642
+ }
95643
+
95644
+ // src/commands/proxy.ts
95645
+ var SUPPORTED = ["mysql", "mariadb", "postgresql"];
95646
+ var ALLOWED_FORMATS18 = ["text", "json"];
95647
+ var ALLOWED_REDACT = ["none", "literals"];
95648
+ function parseHostPort(value) {
95649
+ const idx = value.lastIndexOf(":");
95650
+ if (idx <= 0 || idx === value.length - 1) {
95651
+ throw new Error(`Invalid address "${value}". Expected host:port`);
95652
+ }
95653
+ const host = value.slice(0, idx);
95654
+ const port = Number(value.slice(idx + 1));
95655
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
95656
+ throw new Error(`Invalid port in "${value}". Expected host:port with a numeric port`);
95657
+ }
95658
+ return { host, port };
95659
+ }
95660
+ function resolveProxyConfig(input) {
95661
+ if (!input.listen) {
95662
+ throw new Error("--listen <host:port> is required");
95663
+ }
95664
+ const listen = parseHostPort(input.listen);
95665
+ let engine;
95666
+ if (input.subcommandEngine) {
95667
+ engine = input.subcommandEngine;
95668
+ } else {
95669
+ const sys = input.connection?.system;
95670
+ if (!sys || !SUPPORTED.includes(sys)) {
95671
+ throw new Error(`proxy supports mysql, mariadb, postgresql (got: ${sys ?? "none"})`);
95672
+ }
95673
+ engine = sys;
95674
+ }
95675
+ let target;
95676
+ if (input.target) {
95677
+ target = parseHostPort(input.target);
95678
+ } else if (input.connection) {
95679
+ target = { host: input.connection.host, port: input.connection.port };
95680
+ } else {
95681
+ throw new Error("--target <host:port> is required when config does not provide host/port");
95682
+ }
95683
+ return { engine, listen, target };
95684
+ }
95685
+ async function runProxy(subcommandEngine, options, command) {
95686
+ try {
95687
+ validateFormat(options.format ?? "text", ALLOWED_FORMATS18, "proxy");
95688
+ const redact = options.redact ?? "none";
95689
+ if (!ALLOWED_REDACT.includes(redact)) {
95690
+ throw new Error(`Invalid --redact "${redact}". Allowed: none, literals`);
95691
+ }
95692
+ const configPath = resolveConfigPath(command, options);
95693
+ let connection = null;
95694
+ try {
95695
+ const config = await configModule.read(configPath);
95696
+ if (config.connection) {
95697
+ connection = {
95698
+ system: config.connection.system,
95699
+ host: config.connection.host,
95700
+ port: config.connection.port
95701
+ };
95702
+ }
95703
+ } catch {}
95704
+ const resolved = resolveProxyConfig({
95705
+ subcommandEngine,
95706
+ listen: options.listen,
95707
+ target: options.target,
95708
+ connection
95709
+ });
95710
+ const eventsPath = options.events ?? join32(".dbcli", "proxy", "events.jsonl");
95711
+ const slowMs = Number(options.slowMs ?? 1000);
95712
+ if (!Number.isFinite(slowMs) || slowMs < 0) {
95713
+ throw new Error(`Invalid --slow-ms "${options.slowMs}". Expected a non-negative number`);
95714
+ }
95715
+ const server = new ProxyServer({
95716
+ engine: resolved.engine,
95717
+ listen: resolved.listen,
95718
+ target: resolved.target,
95719
+ eventsPath,
95720
+ slowMs,
95721
+ redact,
95722
+ warn: (m) => process.stderr.write(`[proxy] ${m}
95723
+ `)
95724
+ });
95725
+ await server.start();
95726
+ if (options.format === "json") {
95727
+ process.stdout.write(JSON.stringify({
95728
+ status: "listening",
95729
+ engine: resolved.engine,
95730
+ listen: `${resolved.listen.host}:${resolved.listen.port}`,
95731
+ target: `${resolved.target.host}:${resolved.target.port}`,
95732
+ events: eventsPath,
95733
+ redact
95734
+ }) + `
95735
+ `);
95736
+ } else {
95737
+ process.stdout.write(`dbcli proxy (${resolved.engine}) listening on ${resolved.listen.host}:${resolved.listen.port}` + ` -> ${resolved.target.host}:${resolved.target.port}
95738
+ ` + `events: ${eventsPath} | slow-ms: ${slowMs} | redact: ${redact}
95739
+ ` + `Press Ctrl+C to stop.
95740
+ `);
95741
+ }
95742
+ await new Promise((resolve5) => {
95743
+ const shutdown = () => {
95744
+ process.removeListener("SIGINT", shutdown);
95745
+ process.removeListener("SIGTERM", shutdown);
95746
+ server.stop();
95747
+ resolve5();
95748
+ };
95749
+ process.on("SIGINT", shutdown);
95750
+ process.on("SIGTERM", shutdown);
95751
+ });
95752
+ } catch (error) {
95753
+ if (error instanceof Error)
95754
+ console.error(error.message);
95755
+ process.exit(1);
95756
+ }
95757
+ }
95758
+ function addCommonOptions(cmd) {
95759
+ return cmd.option("--listen <host:port>", "Local proxy listen address (required)").option("--target <host:port>", "Upstream DB target (optional when config provides host/port)").option("--events <path>", "Event JSONL path", join32(".dbcli", "proxy", "events.jsonl")).option("--slow-ms <number>", "Threshold (ms); queries at/above it get slow:true in the event + a terminal warning", "1000").option("--redact <mode>", "SQL redaction: none | literals", "none").option("--format <format>", "Runtime status output: text | json", "text");
95760
+ }
95761
+ var proxyCommand = new Command().name("proxy").description("Local development observability proxy for MySQL/MariaDB/PostgreSQL (observe-only)");
95762
+ proxyCommand.enablePositionalOptions();
95763
+ for (const engine of SUPPORTED) {
95764
+ addCommonOptions(proxyCommand.command(engine).description(`Proxy a ${engine} connection`)).action(async (options, command) => {
95765
+ await runProxy(engine, options, command);
95766
+ });
95767
+ }
95768
+ var ANALYZE_FORMATS = ["json", "text"];
95769
+ function parseNonNegInt(value, flag, fallback) {
95770
+ if (value === undefined)
95771
+ return fallback;
95772
+ const n = Number(value);
95773
+ if (!Number.isInteger(n) || n < 0) {
95774
+ throw new Error(`Invalid --${flag} "${value}". Expected a non-negative integer`);
95775
+ }
95776
+ return n;
95777
+ }
95778
+ async function runAnalyze(options) {
95779
+ try {
95780
+ const format = options.format ?? "json";
95781
+ validateFormat(format, ANALYZE_FORMATS, "proxy analyze");
95782
+ const top = parseNonNegInt(options.top, "top", 20);
95783
+ const slowMs = parseNonNegInt(options.slowMs, "slow-ms", 1000);
95784
+ const nPlusOne = parseNonNegInt(options.nPlusOne, "n-plus-one", 10);
95785
+ const eventsPath = options.events ?? join32(".dbcli", "proxy", "events.jsonl");
95786
+ const { events, malformedLines, files } = await readEvents(eventsPath, {
95787
+ includeRotated: options.includeRotated !== false
95788
+ });
95789
+ if (files.length === 0) {
95790
+ throw new Error(`no events found at ${eventsPath}; run 'dbcli proxy <engine>' first`);
95791
+ }
95792
+ const report = analyzeEvents(events, {
95793
+ slowMs,
95794
+ top,
95795
+ nPlusOne,
95796
+ sourceFiles: files,
95797
+ malformedLines
95798
+ });
95799
+ if (format === "text") {
95800
+ process.stdout.write(renderAnalysisText(report, top) + `
95801
+ `);
95802
+ } else {
95803
+ process.stdout.write(JSON.stringify(report, null, 2) + `
95804
+ `);
95805
+ }
95806
+ } catch (error) {
95807
+ if (error instanceof Error)
95808
+ console.error(error.message);
95809
+ process.exit(1);
95810
+ }
95811
+ }
95812
+ proxyCommand.command("analyze").description("Analyze a proxy event log offline (no DB connection)").option("--events <path>", "Event JSONL path", join32(".dbcli", "proxy", "events.jsonl")).option("--format <format>", "Output format: json | text", "json").option("--top <number>", "Rows shown in text + suggestedCommands depth", "20").option("--slow-ms <number>", "Slow-query threshold (ms) for slowCount", "1000").option("--n-plus-one <number>", "Min repeats per (session,fingerprint) to flag N+1", "10").option("--no-include-rotated", "Do not merge the rotated <events>.1 segment").action(async (options) => {
95813
+ await runAnalyze(options);
95814
+ });
95815
+ addCommonOptions(proxyCommand).action(async (options, command) => {
95816
+ await runProxy(null, options, command);
95817
+ });
95818
+
95819
+ // src/cli.ts
95820
+ init_config();
95821
+ import { join as join33 } from "path";
94608
95822
  var _bgVersionCheckResult;
94609
95823
  function shouldSkipBackgroundChecks() {
94610
95824
  return process.env.DBCLI_NO_UPDATE_CHECK === "1" || process.env.DBCLI_NO_UPDATE_CHECK === "true" || false;
94611
95825
  }
94612
- var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").option("--use <connection>", "Use a specific named connection (v2 config)");
95826
+ var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").option("--use <connection>", "Use a specific named connection (v2 config)").enablePositionalOptions();
94613
95827
  program2.hook("preAction", (thisCommand, actionCommand) => {
94614
95828
  const opts = thisCommand.opts();
94615
95829
  const useConnection = opts.use;
@@ -94633,7 +95847,7 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
94633
95847
  try {
94634
95848
  let cache = null;
94635
95849
  try {
94636
- const cacheFile = Bun.file(join32(configPath, "version-check.json"));
95850
+ const cacheFile = Bun.file(join33(configPath, "version-check.json"));
94637
95851
  if (await cacheFile.exists()) {
94638
95852
  cache = await cacheFile.json();
94639
95853
  }
@@ -94755,6 +95969,7 @@ program2.addCommand(queriesCommand);
94755
95969
  program2.addCommand(explainCommand);
94756
95970
  program2.addCommand(snapshotCommand);
94757
95971
  program2.addCommand(assertCommand);
95972
+ program2.addCommand(proxyCommand);
94758
95973
  if (!process.argv.slice(2).length) {
94759
95974
  program2.outputHelp();
94760
95975
  }