@youtyan/code-viewer 0.7.0 → 0.8.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.
@@ -10340,6 +10340,42 @@ function throwIfAborted(signal, message = "operation aborted") {
10340
10340
  if (signal?.aborted)
10341
10341
  throw abortError(message);
10342
10342
  }
10343
+ function waitForAbortableResource(promise, signal, dispose, message = "operation aborted") {
10344
+ const disposeSafely = (resource) => {
10345
+ try {
10346
+ dispose(resource);
10347
+ } catch {}
10348
+ };
10349
+ if (!signal)
10350
+ return promise;
10351
+ if (signal.aborted) {
10352
+ promise.then(disposeSafely, () => {
10353
+ return;
10354
+ });
10355
+ return Promise.reject(abortError(message));
10356
+ }
10357
+ return new Promise((resolve2, reject) => {
10358
+ let aborted = false;
10359
+ const onAbort = () => {
10360
+ aborted = true;
10361
+ signal.removeEventListener("abort", onAbort);
10362
+ reject(abortError(message));
10363
+ };
10364
+ signal.addEventListener("abort", onAbort, { once: true });
10365
+ promise.then((resource) => {
10366
+ signal.removeEventListener("abort", onAbort);
10367
+ if (aborted) {
10368
+ disposeSafely(resource);
10369
+ return;
10370
+ }
10371
+ resolve2(resource);
10372
+ }, (error) => {
10373
+ signal.removeEventListener("abort", onAbort);
10374
+ if (!aborted)
10375
+ reject(error);
10376
+ });
10377
+ });
10378
+ }
10343
10379
  function isAbortLikeError(err, signal, extraTokens = []) {
10344
10380
  if (signal?.aborted)
10345
10381
  return true;
@@ -10796,6 +10832,8 @@ var init_sql_capture = __esm(() => {
10796
10832
 
10797
10833
  // web-src/server/database/adapters/docker.ts
10798
10834
  import { spawnSync as spawnSync3 } from "node:child_process";
10835
+ import mysql from "mysql2/promise";
10836
+ import pg from "pg";
10799
10837
  function dockerDatabasesCacheKey(serviceName, kind, cwd) {
10800
10838
  return `${serviceName}\x00${kind}\x00${cwd}`;
10801
10839
  }
@@ -10821,55 +10859,211 @@ function setDockerSchemasCache(key, value, ttlMs, now = Date.now()) {
10821
10859
  function fallbackDockerDatabases(defaultDb) {
10822
10860
  return defaultDb ? [defaultDb] : [];
10823
10861
  }
10824
- function buildExecArgs(config, sql) {
10862
+ function buildExecInvocation(config, sql) {
10863
+ if (!config.containerName) {
10864
+ throw new Error("direct SQL connections use the built-in driver");
10865
+ }
10825
10866
  if (config.kind === "postgresql") {
10826
- return [
10867
+ return {
10868
+ args: [
10869
+ dockerCommand(),
10870
+ "exec",
10871
+ "-i",
10872
+ "-e",
10873
+ `PGPASSWORD=${config.password}`,
10874
+ config.containerName,
10875
+ "psql",
10876
+ "-U",
10877
+ config.user,
10878
+ "-d",
10879
+ config.database,
10880
+ "-X",
10881
+ "-q",
10882
+ "-t",
10883
+ "-A",
10884
+ "-F",
10885
+ PG_FIELD_SEPARATOR,
10886
+ "-R",
10887
+ PG_RECORD_SEPARATOR,
10888
+ "-v",
10889
+ "ON_ERROR_STOP=1",
10890
+ "-c",
10891
+ sql
10892
+ ],
10893
+ env: process.env
10894
+ };
10895
+ }
10896
+ return {
10897
+ args: [
10827
10898
  dockerCommand(),
10828
10899
  "exec",
10829
10900
  "-i",
10830
10901
  "-e",
10831
- `PGPASSWORD=${config.password}`,
10902
+ `MYSQL_PWD=${config.password}`,
10832
10903
  config.containerName,
10833
- "psql",
10834
- "-U",
10904
+ "mysql",
10905
+ "-u",
10835
10906
  config.user,
10836
- "-d",
10837
10907
  config.database,
10838
- "-X",
10839
- "-q",
10840
- "-t",
10841
- "-A",
10842
- "-F",
10843
- "\t",
10844
- "-R",
10845
- PG_RECORD_SEPARATOR,
10846
- "-v",
10847
- "ON_ERROR_STOP=1",
10848
- "-c",
10908
+ "--batch",
10909
+ "--default-character-set=utf8mb4",
10910
+ "-e",
10849
10911
  sql
10850
- ];
10912
+ ],
10913
+ env: process.env
10914
+ };
10915
+ }
10916
+ function sqlDriverValue(value, nullValue) {
10917
+ if (value === null || value === undefined)
10918
+ return nullValue;
10919
+ if (value instanceof Date)
10920
+ return value.toISOString();
10921
+ if (value instanceof Uint8Array)
10922
+ return Buffer.from(value).toString("hex");
10923
+ if (typeof value === "object")
10924
+ return JSON.stringify(value);
10925
+ return String(value);
10926
+ }
10927
+ function pgDriverResult(raw) {
10928
+ const results = Array.isArray(raw) ? raw : [raw];
10929
+ const reversed = [...results].reverse();
10930
+ const result = reversed.find((item) => !!item && typeof item === "object" && Array.isArray(item.fields) && item.fields.length > 0) ?? reversed.find((item) => !!item && typeof item === "object" && Array.isArray(item.rows) && item.rows.length > 0) ?? raw;
10931
+ if (!result || typeof result !== "object")
10932
+ return { columns: [], rows: [] };
10933
+ const fields = Array.isArray(result.fields) ? result.fields : [];
10934
+ const rows = Array.isArray(result.rows) ? result.rows : [];
10935
+ return {
10936
+ columns: fields.map((field) => String(field.name ?? "")),
10937
+ rows: rows.map((row) => row.map((value) => sqlDriverValue(value, "\\N")))
10938
+ };
10939
+ }
10940
+ function mysqlDriverResult(raw) {
10941
+ if (!Array.isArray(raw) || raw.length < 2)
10942
+ return { columns: [], rows: [] };
10943
+ const [allRows, allFields] = raw;
10944
+ let rows = allRows;
10945
+ let fields = allFields;
10946
+ if (Array.isArray(allFields) && allFields.length > 0 && Array.isArray(allFields[0])) {
10947
+ const fieldSets = allFields;
10948
+ const rowSets = Array.isArray(allRows) ? allRows : [];
10949
+ let index = -1;
10950
+ for (let i = fieldSets.length - 1;i >= 0; i--) {
10951
+ if (Array.isArray(fieldSets[i])) {
10952
+ index = i;
10953
+ break;
10954
+ }
10955
+ }
10956
+ fields = index >= 0 ? fieldSets[index] : [];
10957
+ rows = index >= 0 ? rowSets[index] : [];
10851
10958
  }
10852
- return [
10853
- dockerCommand(),
10854
- "exec",
10855
- "-i",
10856
- "-e",
10857
- `MYSQL_PWD=${config.password}`,
10858
- config.containerName,
10859
- "mysql",
10860
- "-u",
10861
- config.user,
10862
- config.database,
10863
- "--batch",
10864
- "--default-character-set=utf8mb4",
10865
- "-e",
10866
- sql
10867
- ];
10959
+ const fieldList = Array.isArray(fields) ? fields.filter(Boolean) : [];
10960
+ const rowList = Array.isArray(rows) ? rows : [];
10961
+ return {
10962
+ columns: fieldList.map((field) => String(field.name ?? "")),
10963
+ rows: rowList.map((row) => row.map((value) => sqlDriverValue(value, "NULL")))
10964
+ };
10965
+ }
10966
+ function createSqlDriverExecutor(config) {
10967
+ if (!config.host || !config.port) {
10968
+ throw new Error("direct SQL connection requires host and port");
10969
+ }
10970
+ if (config.kind === "postgresql") {
10971
+ const pool2 = createPgPoolImpl({
10972
+ host: config.host,
10973
+ port: config.port,
10974
+ user: config.user,
10975
+ password: config.password,
10976
+ database: config.database,
10977
+ ssl: config.tls,
10978
+ max: 4,
10979
+ connectionTimeoutMillis: 1e4,
10980
+ query_timeout: 1e4
10981
+ });
10982
+ return {
10983
+ async exec(sql, signal) {
10984
+ throwIfAborted(signal, "query aborted");
10985
+ const client = await waitForAbortableResource(pool2.connect(), signal, (lateClient) => lateClient.release(true), "query aborted");
10986
+ let released = false;
10987
+ const abort = () => {
10988
+ if (released)
10989
+ return;
10990
+ released = true;
10991
+ client.release(true);
10992
+ };
10993
+ signal?.addEventListener("abort", abort, { once: true });
10994
+ try {
10995
+ throwIfAborted(signal, "query aborted");
10996
+ return pgDriverResult(await client.query({ text: sql, rowMode: "array" }));
10997
+ } catch (error) {
10998
+ if (signal?.aborted)
10999
+ throw abortError("query aborted");
11000
+ throw error;
11001
+ } finally {
11002
+ signal?.removeEventListener("abort", abort);
11003
+ if (!released)
11004
+ client.release();
11005
+ }
11006
+ },
11007
+ close() {
11008
+ pool2.end().catch(() => {
11009
+ return;
11010
+ });
11011
+ }
11012
+ };
11013
+ }
11014
+ const pool = createMysqlPoolImpl({
11015
+ host: config.host,
11016
+ port: config.port,
11017
+ user: config.user,
11018
+ password: config.password,
11019
+ database: config.database,
11020
+ ssl: config.tls ? {} : undefined,
11021
+ waitForConnections: true,
11022
+ connectionLimit: 4,
11023
+ connectTimeout: 1e4,
11024
+ multipleStatements: true,
11025
+ rowsAsArray: true,
11026
+ supportBigNumbers: true,
11027
+ bigNumberStrings: true,
11028
+ dateStrings: true
11029
+ });
11030
+ return {
11031
+ async exec(sql, signal) {
11032
+ throwIfAborted(signal, "query aborted");
11033
+ const connection = await waitForAbortableResource(pool.getConnection(), signal, (lateConnection) => lateConnection.destroy(), "query aborted");
11034
+ let released = false;
11035
+ const abort = () => {
11036
+ if (released)
11037
+ return;
11038
+ released = true;
11039
+ connection.destroy();
11040
+ };
11041
+ signal?.addEventListener("abort", abort, { once: true });
11042
+ try {
11043
+ throwIfAborted(signal, "query aborted");
11044
+ return mysqlDriverResult(await connection.query({ sql, rowsAsArray: true, timeout: 1e4 }));
11045
+ } catch (error) {
11046
+ if (signal?.aborted)
11047
+ throw abortError("query aborted");
11048
+ throw error;
11049
+ } finally {
11050
+ signal?.removeEventListener("abort", abort);
11051
+ if (!released)
11052
+ connection.release();
11053
+ }
11054
+ },
11055
+ close() {
11056
+ pool.end().catch(() => {
11057
+ return;
11058
+ });
11059
+ }
11060
+ };
10868
11061
  }
10869
11062
  function execInContainer(config, sql, timeoutMs = 1e4) {
10870
- const args = buildExecArgs(config, sql);
11063
+ const { args, env } = buildExecInvocation(config, sql);
10871
11064
  const proc = spawnSyncImpl2(args[0], args.slice(1), {
10872
11065
  encoding: "utf8",
11066
+ env,
10873
11067
  timeout: timeoutMs,
10874
11068
  stdio: ["ignore", "pipe", "pipe"]
10875
11069
  });
@@ -10895,11 +11089,12 @@ async function readStreamText(stream) {
10895
11089
  text += decoder.decode();
10896
11090
  return text;
10897
11091
  }
10898
- async function execWithBunSpawn(spawnFn, args, timeoutMs, signal) {
11092
+ async function execWithBunSpawn(spawnFn, args, env, timeoutMs, signal) {
10899
11093
  throwIfAborted(signal, "query aborted");
10900
11094
  let proc;
10901
11095
  try {
10902
11096
  proc = spawnFn(args, {
11097
+ env,
10903
11098
  stdin: "ignore",
10904
11099
  stdout: "pipe",
10905
11100
  stderr: "pipe"
@@ -10946,10 +11141,11 @@ async function execWithBunSpawn(spawnFn, args, timeoutMs, signal) {
10946
11141
  signal?.removeEventListener("abort", abort);
10947
11142
  }
10948
11143
  }
10949
- function execWithNodeSpawn(args, timeoutMs, signal) {
11144
+ function execWithNodeSpawn(args, env, timeoutMs, signal) {
10950
11145
  return spawnTextAsync({
10951
11146
  command: args[0],
10952
11147
  args: args.slice(1),
11148
+ env,
10953
11149
  timeoutMs,
10954
11150
  signal,
10955
11151
  killSignal: "SIGKILL",
@@ -10966,10 +11162,11 @@ async function execInContainerAsync(config, sql, timeoutMs = 1e4, signal) {
10966
11162
  throwIfDockerCommandUnavailableResult(result);
10967
11163
  return result;
10968
11164
  }
10969
- const args = buildExecArgs(config, sql);
11165
+ const { args, env } = buildExecInvocation(config, sql);
10970
11166
  const bunSpawn = globalThis.Bun?.spawn;
10971
- result = bunSpawn ? await execWithBunSpawn(bunSpawn, args, timeoutMs, signal) : await execWithNodeSpawn(args, timeoutMs, signal);
10972
- throwIfDockerCommandUnavailableResult(result);
11167
+ result = bunSpawn ? await execWithBunSpawn(bunSpawn, args, env, timeoutMs, signal) : await execWithNodeSpawn(args, env, timeoutMs, signal);
11168
+ if (config.containerName)
11169
+ throwIfDockerCommandUnavailableResult(result);
10973
11170
  return result;
10974
11171
  }
10975
11172
  function stripFinalLineBreak(text) {
@@ -11029,14 +11226,14 @@ function decodeMysqlBatchField(value) {
11029
11226
  }
11030
11227
  return out;
11031
11228
  }
11032
- function splitTsvLine(line, decodeFields) {
11033
- const fields = line.split("\t");
11229
+ function splitTsvLine(line, decodeFields, fieldSeparator = "\t") {
11230
+ const fields = line.split(fieldSeparator);
11034
11231
  return decodeFields ? fields.map(decodeMysqlBatchField) : fields;
11035
11232
  }
11036
11233
  function stripFinalRecordSeparator(text, recordSeparator) {
11037
11234
  return text.endsWith(recordSeparator) ? text.slice(0, -recordSeparator.length) : text;
11038
11235
  }
11039
- function parseTsvOutput(stdout, hasHeader, recordSeparator) {
11236
+ function parseTsvOutput(stdout, hasHeader, recordSeparator, fieldSeparator) {
11040
11237
  const text = recordSeparator ? stripFinalRecordSeparator(stripFinalLineBreak(stdout), recordSeparator) : stripFinalLineBreak(stdout);
11041
11238
  if (text.length === 0)
11042
11239
  return { columns: [], rows: [] };
@@ -11044,11 +11241,11 @@ function parseTsvOutput(stdout, hasHeader, recordSeparator) {
11044
11241
  if (lines.length === 0)
11045
11242
  return { columns: [], rows: [] };
11046
11243
  if (hasHeader) {
11047
- const columns = splitTsvLine(lines[0], true);
11048
- const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true));
11244
+ const columns = splitTsvLine(lines[0], true, fieldSeparator);
11245
+ const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true, fieldSeparator));
11049
11246
  return { columns, rows: rows2 };
11050
11247
  }
11051
- const rows = lines.map((line) => splitTsvLine(line, false));
11248
+ const rows = lines.map((line) => splitTsvLine(line, false, fieldSeparator));
11052
11249
  return { columns: [], rows };
11053
11250
  }
11054
11251
  function isMysqlSpatialType(type) {
@@ -11107,13 +11304,18 @@ function observeBackgroundRejection(promise) {
11107
11304
  });
11108
11305
  return promise;
11109
11306
  }
11110
- function createDockerAdapter(config) {
11307
+ function createSqlCliAdapter(config) {
11308
+ const driver = config.containerName ? null : createSqlDriverExecutor(config);
11111
11309
  async function execAsync(sql, signal) {
11310
+ if (driver) {
11311
+ recordSql(sql);
11312
+ return driver.exec(sql, signal);
11313
+ }
11112
11314
  const result = await execInContainerAsync(config, sql, 1e4, signal);
11113
11315
  if (result.code !== 0) {
11114
11316
  throw new Error(result.stderr.trim() || "query failed");
11115
11317
  }
11116
- return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
11318
+ return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, config.kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
11117
11319
  }
11118
11320
  function toDbValue(val) {
11119
11321
  if (val === "NULL" || val === "\\N")
@@ -11190,15 +11392,16 @@ function createDockerAdapter(config) {
11190
11392
  async getTablesAsync(signal) {
11191
11393
  let sql;
11192
11394
  if (config.kind === "postgresql") {
11193
- sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ${postgresSchemaLiteral()} ORDER BY table_name`;
11395
+ sql = `SELECT t.table_name, t.table_type, COALESCE(obj_description(cls.oid, 'pg_class'), '') FROM information_schema.tables t JOIN pg_namespace n ON n.nspname = t.table_schema JOIN pg_class cls ON cls.relnamespace = n.oid AND cls.relname = t.table_name WHERE t.table_schema = ${postgresSchemaLiteral()} ORDER BY t.table_name`;
11194
11396
  } else {
11195
- sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
11397
+ sql = `SELECT table_name, table_type, table_comment FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
11196
11398
  }
11197
11399
  const result = await execAsync(sql, signal);
11198
11400
  return result.rows.map((row) => ({
11199
11401
  name: row[0],
11200
11402
  type: row[1] === "VIEW" ? "view" : "table",
11201
- rowCount: null
11403
+ rowCount: null,
11404
+ comment: row[2] || null
11202
11405
  }));
11203
11406
  },
11204
11407
  async getColumnsAsync(table, signal) {
@@ -11211,11 +11414,11 @@ function createDockerAdapter(config) {
11211
11414
  },
11212
11415
  async getIndexesAsync(signal) {
11213
11416
  let sql;
11214
- const INDEX_COL_SEP = "\x1F";
11417
+ const INDEX_COL_SEP = "\x1D";
11215
11418
  if (config.kind === "postgresql") {
11216
- sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\x1f' ORDER BY k.ord), '') FROM pg_index ix JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = ${postgresSchemaLiteral()} AND i.relname NOT LIKE 'pg_%' GROUP BY i.relname, t.relname, ix.indisunique ORDER BY t.relname, i.relname`;
11419
+ sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\x1d' ORDER BY k.ord), '') FROM pg_index ix JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = ${postgresSchemaLiteral()} AND i.relname NOT LIKE 'pg_%' GROUP BY i.relname, t.relname, ix.indisunique ORDER BY t.relname, i.relname`;
11217
11420
  } else {
11218
- sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\x1F') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
11421
+ sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\x1D') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
11219
11422
  }
11220
11423
  const result = await execAsync(sql, signal);
11221
11424
  return result.rows.map((row) => ({
@@ -11544,6 +11747,7 @@ function createDockerAdapter(config) {
11544
11747
  close() {
11545
11748
  columnCache.clear();
11546
11749
  tableMetaCache.invalidate();
11750
+ driver?.close();
11547
11751
  },
11548
11752
  async* iterateForSnapshot(table, signal) {
11549
11753
  const columns = await adapter.getColumnsAsync(table, signal);
@@ -11615,7 +11819,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
11615
11819
  return fallback;
11616
11820
  return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
11617
11821
  }
11618
- const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
11822
+ const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
11619
11823
  const dbs = parsed.rows.map((r) => r[0]).filter(Boolean);
11620
11824
  const value = dbs.length > 0 ? dbs : fallbackDockerDatabases(defaultDb);
11621
11825
  return setDockerDatabasesCache(cacheKey, value, value.length > 0 ? DOCKER_DATABASES_POSITIVE_TTL_MS : DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
@@ -11655,7 +11859,7 @@ async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, sign
11655
11859
  if (result.code !== 0) {
11656
11860
  return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
11657
11861
  }
11658
- const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR);
11862
+ const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR, PG_FIELD_SEPARATOR);
11659
11863
  const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
11660
11864
  const value = schemas.length > 0 ? schemas : ["public"];
11661
11865
  return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
@@ -11713,7 +11917,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
11713
11917
  ...kind === "postgresql" && schema ? { schema } : {}
11714
11918
  });
11715
11919
  }
11716
- var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
11920
+ var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config), COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", PG_FIELD_SEPARATOR = "\x1F", MYSQL_SPATIAL_TYPES, createDockerAdapter, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
11717
11921
  var init_docker = __esm(() => {
11718
11922
  init_mutate();
11719
11923
  init_sql_snapshot();
@@ -11735,6 +11939,7 @@ var init_docker = __esm(() => {
11735
11939
  "geometrycollection",
11736
11940
  "geomcollection"
11737
11941
  ]);
11942
+ createDockerAdapter = createSqlCliAdapter;
11738
11943
  });
11739
11944
 
11740
11945
  // web-src/server/database/adapters/elasticsearch.ts
@@ -11743,8 +11948,13 @@ __export(exports_elasticsearch, {
11743
11948
  quoteCurlConfigString: () => quoteCurlConfigString,
11744
11949
  openElasticsearchAdapterAsync: () => openElasticsearchAdapterAsync,
11745
11950
  isReadOnlyEsPath: () => isReadOnlyEsPath,
11746
- canonicalizeEsSnapshotContainer: () => canonicalizeEsSnapshotContainer
11951
+ createElasticsearchAdapter: () => createElasticsearchAdapter,
11952
+ canonicalizeEsSnapshotContainer: () => canonicalizeEsSnapshotContainer,
11953
+ __setEsFetchForTest: () => __setEsFetchForTest
11747
11954
  });
11955
+ function __setEsFetchForTest(fetchForTest) {
11956
+ esFetchImpl = fetchForTest ?? globalThis.fetch;
11957
+ }
11748
11958
  function isReadOnlyEsPath(rawPath) {
11749
11959
  const path = rawPath.split("?")[0].replace(/\/+$/, "");
11750
11960
  const segments = path.split("/").filter(Boolean);
@@ -11764,13 +11974,11 @@ function quoteCurlConfigString(value) {
11764
11974
  }
11765
11975
  function buildEsRequestInvocation(config, method, path, body) {
11766
11976
  const hasPassword = !!config.password;
11767
- const url = `http://localhost:9200${path.startsWith("/") ? "" : "/"}${path}`;
11768
- const curlConfig = hasPassword ? `user = ${quoteCurlConfigString(`elastic:${config.password}`)}
11977
+ const endpoint = config.endpoint?.replace(/\/$/, "") || "http://localhost:9200";
11978
+ const url = `${endpoint}${path.startsWith("/") ? "" : "/"}${path}`;
11979
+ const curlConfig = hasPassword ? `user = ${quoteCurlConfigString(`${config.username || "elastic"}:${config.password}`)}
11769
11980
  ` : undefined;
11770
- const args = [
11771
- "exec",
11772
- "-i",
11773
- config.containerName,
11981
+ const curlArgs = [
11774
11982
  "curl",
11775
11983
  "-s",
11776
11984
  "-S",
@@ -11786,13 +11994,64 @@ __ES_STATUS__:%{http_code}
11786
11994
  ...hasPassword ? ["-K", "-"] : [],
11787
11995
  ...body !== undefined ? ["--data-binary", JSON.stringify(body)] : []
11788
11996
  ];
11789
- return { args, input: curlConfig };
11997
+ if (!config.containerName) {
11998
+ throw new Error("direct Elasticsearch connections use the built-in HTTP client");
11999
+ }
12000
+ return {
12001
+ command: dockerCommand(),
12002
+ args: ["exec", "-i", config.containerName, ...curlArgs],
12003
+ input: curlConfig
12004
+ };
11790
12005
  }
11791
12006
  async function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signal) {
11792
12007
  throwIfAborted(signal, "elasticsearch request aborted");
12008
+ if (!config.containerName) {
12009
+ const endpoint = config.endpoint?.replace(/\/$/, "") || "http://localhost:9200";
12010
+ const url = `${endpoint}${path.startsWith("/") ? "" : "/"}${path}`;
12011
+ const controller = new AbortController;
12012
+ let timedOut = false;
12013
+ const timer = setTimeout(() => {
12014
+ timedOut = true;
12015
+ controller.abort();
12016
+ }, timeoutMs);
12017
+ const abort = () => controller.abort();
12018
+ signal?.addEventListener("abort", abort, { once: true });
12019
+ try {
12020
+ const headers = new Headers({ "Content-Type": "application/json" });
12021
+ if (config.password) {
12022
+ headers.set("Authorization", `Basic ${Buffer.from(`${config.username || "elastic"}:${config.password}`).toString("base64")}`);
12023
+ }
12024
+ const response = await esFetchImpl(url, {
12025
+ method,
12026
+ headers,
12027
+ body: body === undefined ? undefined : JSON.stringify(body),
12028
+ signal: controller.signal,
12029
+ redirect: "error"
12030
+ });
12031
+ const text = await response.text();
12032
+ return {
12033
+ code: 0,
12034
+ stdout: `${text}
12035
+ __ES_STATUS__:${response.status}
12036
+ `,
12037
+ stderr: ""
12038
+ };
12039
+ } catch (error) {
12040
+ if (signal?.aborted)
12041
+ throw new Error("elasticsearch request aborted");
12042
+ return {
12043
+ code: 1,
12044
+ stdout: "",
12045
+ stderr: timedOut ? `elasticsearch request timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error)
12046
+ };
12047
+ } finally {
12048
+ clearTimeout(timer);
12049
+ signal?.removeEventListener("abort", abort);
12050
+ }
12051
+ }
11793
12052
  const invocation = buildEsRequestInvocation(config, method, path, body);
11794
12053
  const result = await spawnTextAsync({
11795
- command: dockerCommand(),
12054
+ command: invocation.command,
11796
12055
  args: invocation.args,
11797
12056
  env: process.env,
11798
12057
  input: invocation.input,
@@ -11802,7 +12061,8 @@ async function execEsRequestAsync(config, method, path, body, timeoutMs = 15000,
11802
12061
  timeoutMessage: `elasticsearch request timed out after ${timeoutMs}ms`,
11803
12062
  rejectOnError: false
11804
12063
  });
11805
- throwIfDockerCommandUnavailableResult(result);
12064
+ if (config.containerName)
12065
+ throwIfDockerCommandUnavailableResult(result);
11806
12066
  return result;
11807
12067
  }
11808
12068
  function parseEsResponse(stdout) {
@@ -11829,7 +12089,7 @@ function createElasticsearchAdapter(config) {
11829
12089
  async function callJsonAsync(method, path, body, label, signal) {
11830
12090
  const r = await execEsRequestAsync(config, method, path, body, 15000, signal);
11831
12091
  if (r.code !== 0) {
11832
- throw new Error(r.stderr.trim() || `${label}: curl exit ${r.code}`);
12092
+ throw new Error(r.stderr.trim() || `${label}: request failed`);
11833
12093
  }
11834
12094
  const { status, body: text } = parseEsResponse(r.stdout);
11835
12095
  if (status < 200 || status >= 300) {
@@ -11914,7 +12174,7 @@ function createElasticsearchAdapter(config) {
11914
12174
  }
11915
12175
  const r = await execEsRequestAsync(config, "GET", `/${encodeURIComponent(opts.index)}/_doc/${encodeURIComponent(opts.id)}`, undefined, 15000, opts.signal);
11916
12176
  if (r.code !== 0) {
11917
- throw new Error(r.stderr.trim() || `_doc: curl exit ${r.code}`);
12177
+ throw new Error(r.stderr.trim() || "_doc: request failed");
11918
12178
  }
11919
12179
  const { status, body: text } = parseEsResponse(r.stdout);
11920
12180
  if (status === 404) {
@@ -12013,7 +12273,7 @@ function createElasticsearchAdapter(config) {
12013
12273
  const r = await execEsRequestAsync(config, input.method, input.path, input.body, 15000, signal);
12014
12274
  const elapsedMs = Date.now() - start;
12015
12275
  if (r.code !== 0) {
12016
- throw new Error(r.stderr.trim() || `query: curl exit ${r.code}`);
12276
+ throw new Error(r.stderr.trim() || "query: request failed");
12017
12277
  }
12018
12278
  const { status, body: text } = parseEsResponse(r.stdout);
12019
12279
  let body = text;
@@ -12099,10 +12359,11 @@ async function openElasticsearchAdapterAsync(serviceName, env, cwd, signal) {
12099
12359
  const password = env.ELASTIC_PASSWORD || "";
12100
12360
  return createElasticsearchAdapter({ containerName, password });
12101
12361
  }
12102
- var ES_QUERY_ALLOWED_SUBPATHS, ES_DEFAULT_SIZE = 200;
12362
+ var esFetchImpl, ES_QUERY_ALLOWED_SUBPATHS, ES_DEFAULT_SIZE = 200;
12103
12363
  var init_elasticsearch = __esm(() => {
12104
12364
  init_docker_utils();
12105
12365
  init_spawn_runner();
12366
+ esFetchImpl = globalThis.fetch;
12106
12367
  ES_QUERY_ALLOWED_SUBPATHS = new Set([
12107
12368
  "_search",
12108
12369
  "_count",
@@ -12119,9 +12380,14 @@ var exports_redis = {};
12119
12380
  __export(exports_redis, {
12120
12381
  openRedisExplorerAsync: () => openRedisExplorerAsync,
12121
12382
  createRedisAdapter: () => createRedisAdapter,
12122
- canonicalizeRedisSnapshotContainer: () => canonicalizeRedisSnapshotContainer
12383
+ canonicalizeRedisSnapshotContainer: () => canonicalizeRedisSnapshotContainer,
12384
+ __setRedisClientFactoryForTest: () => __setRedisClientFactoryForTest
12123
12385
  });
12124
12386
  import { createHash as createHash3 } from "node:crypto";
12387
+ import { createClient } from "@redis/client";
12388
+ function __setRedisClientFactoryForTest(factory) {
12389
+ createRedisClientImpl = factory ?? ((options) => createClient(options));
12390
+ }
12125
12391
  function canonicalizeRedisSnapshotContainer(container) {
12126
12392
  const { db, pattern } = parseSnapshotContainer(container);
12127
12393
  return JSON.stringify({ db, pattern });
@@ -12139,20 +12405,25 @@ function parseSnapshotContainer(container) {
12139
12405
  }
12140
12406
  function buildRedisCliInvocation(config, args) {
12141
12407
  const hasPassword = !!config.password;
12142
- const dockerArgs = [
12143
- "exec",
12144
- "-i",
12145
- ...hasPassword ? ["-e", "REDISCLI_AUTH"] : [],
12146
- config.containerName,
12147
- "redis-cli",
12148
- "-3",
12149
- ...args
12150
- ];
12151
12408
  const spawnEnv = hasPassword ? { ...process.env, REDISCLI_AUTH: config.password } : process.env;
12152
- return { args: dockerArgs, env: spawnEnv };
12409
+ if (!config.containerName) {
12410
+ throw new Error("direct Redis connections use the built-in driver");
12411
+ }
12412
+ return {
12413
+ args: [
12414
+ "exec",
12415
+ "-i",
12416
+ ...hasPassword ? ["-e", "REDISCLI_AUTH"] : [],
12417
+ config.containerName,
12418
+ "redis-cli",
12419
+ "-3",
12420
+ ...args
12421
+ ],
12422
+ env: spawnEnv
12423
+ };
12153
12424
  }
12154
- async function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
12155
- throwIfAborted(signal, "redis-cli aborted");
12425
+ async function execRedisCliProcessAsync(config, args, timeoutMs = 1e4, signal) {
12426
+ throwIfAborted(signal, "redis request aborted");
12156
12427
  const invocation = buildRedisCliInvocation(config, args);
12157
12428
  const result = await spawnTextAsync({
12158
12429
  command: dockerCommand(),
@@ -12160,13 +12431,115 @@ async function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
12160
12431
  env: invocation.env,
12161
12432
  timeoutMs,
12162
12433
  signal,
12163
- abortMessage: "redis-cli aborted",
12164
- timeoutMessage: `redis-cli timed out after ${timeoutMs}ms`,
12434
+ abortMessage: "redis request aborted",
12435
+ timeoutMessage: `redis request timed out after ${timeoutMs}ms`,
12165
12436
  rejectOnError: false
12166
12437
  });
12167
- throwIfDockerCommandUnavailableResult(result);
12438
+ if (config.containerName)
12439
+ throwIfDockerCommandUnavailableResult(result);
12168
12440
  return result;
12169
12441
  }
12442
+ function redisReplyText(reply) {
12443
+ if (reply === null || reply === undefined)
12444
+ return "";
12445
+ if (reply instanceof Uint8Array)
12446
+ return Buffer.from(reply).toString("utf8");
12447
+ if (Array.isArray(reply))
12448
+ return JSON.stringify(reply);
12449
+ if (typeof reply === "object")
12450
+ return JSON.stringify(reply);
12451
+ return String(reply);
12452
+ }
12453
+ function createRedisDriverExecutor(config) {
12454
+ if (!config.host || !config.port) {
12455
+ throw new Error("direct Redis connection requires host and port");
12456
+ }
12457
+ const clients = new Map;
12458
+ function clientForDatabase(database) {
12459
+ const existing = clients.get(database);
12460
+ if (existing)
12461
+ return existing;
12462
+ const socket = config.tls ? {
12463
+ host: config.host,
12464
+ port: config.port,
12465
+ tls: true,
12466
+ connectTimeout: 1e4
12467
+ } : {
12468
+ host: config.host,
12469
+ port: config.port,
12470
+ connectTimeout: 1e4
12471
+ };
12472
+ const client = createRedisClientImpl({
12473
+ username: config.username || undefined,
12474
+ password: config.password || undefined,
12475
+ database,
12476
+ socket
12477
+ });
12478
+ client.on("error", () => {});
12479
+ const entry = { client, connected: client.connect() };
12480
+ clients.set(database, entry);
12481
+ return entry;
12482
+ }
12483
+ return {
12484
+ async exec(args, timeoutMs = 1e4, signal) {
12485
+ throwIfAborted(signal, "redis request aborted");
12486
+ let database = 0;
12487
+ let command = args;
12488
+ if (args[0] === "-n" && args.length >= 3) {
12489
+ database = Number(args[1]) || 0;
12490
+ command = args.slice(2);
12491
+ }
12492
+ const entry = clientForDatabase(database);
12493
+ let connectDisposed = false;
12494
+ const disposeConnectingClient = () => {
12495
+ if (connectDisposed)
12496
+ return;
12497
+ connectDisposed = true;
12498
+ if (clients.get(database) === entry)
12499
+ clients.delete(database);
12500
+ entry.client.destroy();
12501
+ };
12502
+ const abortConnect = () => disposeConnectingClient();
12503
+ signal?.addEventListener("abort", abortConnect, { once: true });
12504
+ const timeoutController = new AbortController;
12505
+ let timedOut = false;
12506
+ const timer = setTimeout(() => {
12507
+ timedOut = true;
12508
+ timeoutController.abort();
12509
+ }, timeoutMs);
12510
+ const abort = () => timeoutController.abort();
12511
+ signal?.addEventListener("abort", abort, { once: true });
12512
+ let connected = false;
12513
+ try {
12514
+ await waitForAbortableResource(entry.connected.then(() => entry.client), signal, disposeConnectingClient, "redis request aborted");
12515
+ connected = true;
12516
+ signal?.removeEventListener("abort", abortConnect);
12517
+ throwIfAborted(signal, "redis request aborted");
12518
+ const reply = await entry.client.withAbortSignal(timeoutController.signal).sendCommand(command);
12519
+ return { stdout: redisReplyText(reply), stderr: "", code: 0 };
12520
+ } catch (error) {
12521
+ if (!connected)
12522
+ disposeConnectingClient();
12523
+ if (signal?.aborted)
12524
+ throw abortError("redis request aborted");
12525
+ return {
12526
+ stdout: "",
12527
+ stderr: timedOut ? `redis request timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error),
12528
+ code: 1
12529
+ };
12530
+ } finally {
12531
+ clearTimeout(timer);
12532
+ signal?.removeEventListener("abort", abortConnect);
12533
+ signal?.removeEventListener("abort", abort);
12534
+ }
12535
+ },
12536
+ close() {
12537
+ for (const { client } of clients.values())
12538
+ client.destroy();
12539
+ clients.clear();
12540
+ }
12541
+ };
12542
+ }
12170
12543
  function parseInfoKeyspace(stdout) {
12171
12544
  const counts = new Map;
12172
12545
  for (const line of stdout.split(/\r?\n/)) {
@@ -12201,6 +12574,8 @@ function decodeHexItem(hex) {
12201
12574
  return { binaryBase64: buf.toString("base64") };
12202
12575
  }
12203
12576
  function createRedisAdapter(config) {
12577
+ const driver = config.containerName ? null : createRedisDriverExecutor(config);
12578
+ const execRedisCliAsync = (_config, args, timeoutMs = 1e4, signal) => driver ? driver.exec(args, timeoutMs, signal) : execRedisCliProcessAsync(config, args, timeoutMs, signal);
12204
12579
  function parseDatabasesResult(result) {
12205
12580
  if (result.code !== 0) {
12206
12581
  throw new Error(result.stderr.trim() || "INFO keyspace failed");
@@ -12691,7 +13066,9 @@ function createRedisAdapter(config) {
12691
13066
  deleteKeyAsync,
12692
13067
  iterateForSnapshot,
12693
13068
  listSnapshotContainers,
12694
- close() {}
13069
+ close() {
13070
+ driver?.close();
13071
+ }
12695
13072
  };
12696
13073
  }
12697
13074
  async function openRedisExplorerAsync(serviceName, env, cwd, signal) {
@@ -12699,7 +13076,7 @@ async function openRedisExplorerAsync(serviceName, env, cwd, signal) {
12699
13076
  const password = env.REDIS_PASSWORD || "";
12700
13077
  return createRedisAdapter({ containerName, password });
12701
13078
  }
12702
- var DEFAULT_DATABASES = 16, REDIS_STRING_BYTE_LIMIT = 65536, REDIS_COLLECTION_LIMIT = 200, SCAN_WITH_TYPES_LUA = `local s = redis.call('SCAN', ARGV[1], 'MATCH', ARGV[2], 'COUNT', ARGV[3]); local types = {}; for i, k in ipairs(s[2]) do types[i] = redis.call('TYPE', k).ok end; return cjson.encode({cursor=s[1], keys=s[2], types=types})`, LUA_TOHEX_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end`, TYPE_OR_STRING_LUA, LUA_HEX_KEY_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end local function fromhex(h) local b = {} for i = 1, #h, 2 do b[#b+1] = string.char(tonumber(string.sub(h, i, i+1), 16)) end return table.concat(b) end`, SCAN_HEX_KEYS_LUA;
13079
+ var createRedisClientImpl = (options) => createClient(options), DEFAULT_DATABASES = 16, REDIS_STRING_BYTE_LIMIT = 65536, REDIS_COLLECTION_LIMIT = 200, SCAN_WITH_TYPES_LUA = `local s = redis.call('SCAN', ARGV[1], 'MATCH', ARGV[2], 'COUNT', ARGV[3]); local types = {}; for i, k in ipairs(s[2]) do types[i] = redis.call('TYPE', k).ok end; return cjson.encode({cursor=s[1], keys=s[2], types=types})`, LUA_TOHEX_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end`, TYPE_OR_STRING_LUA, LUA_HEX_KEY_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end local function fromhex(h) local b = {} for i = 1, #h, 2 do b[#b+1] = string.char(tonumber(string.sub(h, i, i+1), 16)) end return table.concat(b) end`, SCAN_HEX_KEYS_LUA;
12703
13080
  var init_redis = __esm(() => {
12704
13081
  init_docker_utils();
12705
13082
  init_spawn_runner();
@@ -15779,185 +16156,33 @@ function asAsyncDoc(source) {
15779
16156
  };
15780
16157
  }
15781
16158
 
15782
- // web-src/server/database/connection-pool.ts
15783
- function setAdapterFactory(f) {
15784
- factory = f;
16159
+ // web-src/server/database/adapters/dynamodb.ts
16160
+ import { spawnSync as spawnSync5 } from "node:child_process";
16161
+ import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
16162
+ function createDynamoDbRequestDeadline() {
16163
+ const timeoutMs = dynamoDbRequestTimeoutMs;
16164
+ return { expiresAt: Date.now() + timeoutMs, timeoutMs };
15785
16165
  }
15786
- function evictOldest() {
15787
- let oldestKey = null;
15788
- let oldestTime = Infinity;
15789
- for (const [key, entry] of pool) {
15790
- if (entry.lastUsed < oldestTime) {
15791
- oldestTime = entry.lastUsed;
15792
- oldestKey = key;
15793
- }
15794
- }
15795
- if (oldestKey) {
15796
- const entry = pool.get(oldestKey);
15797
- if (entry) {
15798
- clearTimeout(entry.timer);
15799
- try {
15800
- entry.adapter.close();
15801
- } catch {}
15802
- pool.delete(oldestKey);
15803
- }
15804
- }
16166
+ function createDynamoDbDockerCurlDeadline() {
16167
+ const timeoutMs = dynamoDbDockerCurlTimeoutMs;
16168
+ return { expiresAt: Date.now() + timeoutMs, timeoutMs };
15805
16169
  }
15806
- function scheduleEviction(key, entry) {
15807
- clearTimeout(entry.timer);
15808
- entry.timer = setTimeout(() => {
15809
- const current = pool.get(key);
15810
- if (current === entry) {
15811
- try {
15812
- current.adapter.close();
15813
- } catch {}
15814
- pool.delete(key);
15815
- }
15816
- }, IDLE_TIMEOUT_MS);
16170
+ function createDynamoDbTransportDeadline(config) {
16171
+ return config.dockerContainerName ? createDynamoDbDockerCurlDeadline() : createDynamoDbRequestDeadline();
15817
16172
  }
15818
- async function getConnection(resolvedPath) {
15819
- if (!factory) {
15820
- throw new Error("No adapter factory configured");
15821
- }
15822
- const existing = pool.get(resolvedPath);
15823
- if (existing) {
15824
- existing.lastUsed = Date.now();
15825
- scheduleEviction(resolvedPath, existing);
15826
- return existing.adapter;
15827
- }
15828
- if (pool.size >= MAX_CONNECTIONS) {
15829
- evictOldest();
15830
- }
15831
- const adapter = await factory.open(resolvedPath);
15832
- const entry = {
15833
- adapter,
15834
- path: resolvedPath,
15835
- lastUsed: Date.now(),
15836
- timer: setTimeout(() => {
15837
- return;
15838
- }, 0)
15839
- };
15840
- pool.set(resolvedPath, entry);
15841
- scheduleEviction(resolvedPath, entry);
15842
- return adapter;
16173
+ function dynamoDbTimeoutError(deadline) {
16174
+ return new DynamoDbHttpError(503, `DynamoDB request timed out after ${deadline?.timeoutMs ?? dynamoDbRequestTimeoutMs}ms`);
15843
16175
  }
15844
- function closeConnection(resolvedPath) {
15845
- const entry = pool.get(resolvedPath);
15846
- if (!entry)
15847
- return false;
15848
- clearTimeout(entry.timer);
15849
- try {
15850
- entry.adapter.close();
15851
- } catch {}
15852
- pool.delete(resolvedPath);
15853
- return true;
16176
+ function remainingDynamoDbTimeoutMs(deadline) {
16177
+ if (!deadline)
16178
+ return dynamoDbRequestTimeoutMs;
16179
+ return Math.max(0, deadline.expiresAt - Date.now());
15854
16180
  }
15855
- var MAX_CONNECTIONS = 8, IDLE_TIMEOUT_MS, pool, factory = null;
15856
- var init_connection_pool = __esm(() => {
15857
- IDLE_TIMEOUT_MS = 5 * 60 * 1000;
15858
- pool = new Map;
15859
- });
15860
-
15861
- // web-src/server/database/global-search.ts
15862
- function isTextLikeType(type) {
15863
- const upper = type.toUpperCase();
15864
- return upper.includes("CHAR") || upper.includes("TEXT") || upper.includes("VARCHAR") || upper.includes("CLOB") || upper.includes("STRING") || upper === "JSON" || upper === "JSONB" || upper === "XML" || upper === "UUID";
16181
+ function hmac2(key, value) {
16182
+ return createHmac2("sha256", key).update(value, "utf8").digest();
15865
16183
  }
15866
- function escapeLikeTerm(term) {
15867
- return term.replace(/=/g, "==").replace(/%/g, "=%").replace(/_/g, "=_");
15868
- }
15869
- async function searchTableAsync(adapter, table, columns, term, maxHits, includeNonText, pkColumns, signal) {
15870
- const kind = adapter.kind;
15871
- const searchCols = includeNonText ? columns.filter((c) => c.type.toUpperCase() !== "BLOB" && c.type.toUpperCase() !== "BYTEA") : columns.filter((c) => isTextLikeType(c.type));
15872
- if (searchCols.length === 0)
15873
- return [];
15874
- const escapedTerm = escapeLikeTerm(term);
15875
- const tbl = sanitizeIdentifier(table, kind);
15876
- const hits = [];
15877
- const db = asAsync(adapter);
15878
- for (const col of searchCols) {
15879
- if (signal?.aborted)
15880
- break;
15881
- if (hits.length >= maxHits)
15882
- break;
15883
- const colId = sanitizeIdentifier(col.name, kind);
15884
- const castCol = kind === "mysql" ? `CAST(${colId} AS CHAR)` : `CAST(${colId} AS TEXT)`;
15885
- let sql;
15886
- const remaining = maxHits - hits.length;
15887
- if (kind === "sqlite") {
15888
- sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ? ESCAPE '='`;
15889
- } else {
15890
- const likeVal = escapeSqlString(`%${escapedTerm}%`);
15891
- sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ${likeVal} ESCAPE '='`;
15892
- }
15893
- try {
15894
- const params = kind === "sqlite" ? [`%${escapedTerm}%`] : undefined;
15895
- const result = await db.readonlyQuery(sql, params, remaining, signal);
15896
- for (const row of result.rows) {
15897
- const colIdx = result.columns.indexOf(col.name);
15898
- const valueRaw = colIdx >= 0 ? serializeDbValue(row[colIdx]) : null;
15899
- const valueStr = valueRaw == null ? "" : String(valueRaw);
15900
- const preview = valueStr.length > 200 ? `${valueStr.slice(0, 200)}...` : valueStr;
15901
- let rowKeyJson;
15902
- if (pkColumns.length > 0) {
15903
- const keyObj = {};
15904
- for (const pk of pkColumns) {
15905
- const pkIdx = result.columns.indexOf(pk);
15906
- if (pkIdx >= 0)
15907
- keyObj[pk] = serializeDbValue(row[pkIdx]);
15908
- }
15909
- rowKeyJson = JSON.stringify(keyObj);
15910
- }
15911
- hits.push({
15912
- table,
15913
- column: col.name,
15914
- rowKeyJson,
15915
- valuePreview: preview,
15916
- rowPreview: serializeDbRow(row)
15917
- });
15918
- }
15919
- } catch (err) {
15920
- if (isAbortLikeError(err, signal))
15921
- throw err;
15922
- }
15923
- }
15924
- return hits;
15925
- }
15926
- function getPrimaryKeyColumnsFromColumns(columns) {
15927
- return columns.filter((c) => c.primaryKey).map((c) => c.name);
15928
- }
15929
- var init_global_search = __esm(() => {
15930
- init_serialize();
15931
- init_sql_utils();
15932
- });
15933
-
15934
- // web-src/server/database/adapters/dynamodb.ts
15935
- import { spawnSync as spawnSync5 } from "node:child_process";
15936
- import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
15937
- function createDynamoDbRequestDeadline() {
15938
- const timeoutMs = dynamoDbRequestTimeoutMs;
15939
- return { expiresAt: Date.now() + timeoutMs, timeoutMs };
15940
- }
15941
- function createDynamoDbDockerCurlDeadline() {
15942
- const timeoutMs = dynamoDbDockerCurlTimeoutMs;
15943
- return { expiresAt: Date.now() + timeoutMs, timeoutMs };
15944
- }
15945
- function createDynamoDbTransportDeadline(config) {
15946
- return config.dockerContainerName ? createDynamoDbDockerCurlDeadline() : createDynamoDbRequestDeadline();
15947
- }
15948
- function dynamoDbTimeoutError(deadline) {
15949
- return new DynamoDbHttpError(503, `DynamoDB request timed out after ${deadline?.timeoutMs ?? dynamoDbRequestTimeoutMs}ms`);
15950
- }
15951
- function remainingDynamoDbTimeoutMs(deadline) {
15952
- if (!deadline)
15953
- return dynamoDbRequestTimeoutMs;
15954
- return Math.max(0, deadline.expiresAt - Date.now());
15955
- }
15956
- function hmac2(key, value) {
15957
- return createHmac2("sha256", key).update(value, "utf8").digest();
15958
- }
15959
- function sha2562(value) {
15960
- return createHash5("sha256").update(value, "utf8").digest("hex");
16184
+ function sha2562(value) {
16185
+ return createHash5("sha256").update(value, "utf8").digest("hex");
15961
16186
  }
15962
16187
  function amzDate2(date = new Date) {
15963
16188
  const iso = date.toISOString().replace(/[:-]|\.\d{3}/g, "");
@@ -16393,6 +16618,416 @@ var init_dynamodb = __esm(() => {
16393
16618
  dynamoDbDockerCurlTimeoutMs = DEFAULT_DYNAMODB_DOCKER_CURL_TIMEOUT_MS;
16394
16619
  });
16395
16620
 
16621
+ // web-src/server/database/connection-pool.ts
16622
+ function setAdapterFactory(f) {
16623
+ factory = f;
16624
+ }
16625
+ function evictOldest() {
16626
+ let oldestKey = null;
16627
+ let oldestTime = Infinity;
16628
+ for (const [key, entry] of pool) {
16629
+ if (entry.lastUsed < oldestTime) {
16630
+ oldestTime = entry.lastUsed;
16631
+ oldestKey = key;
16632
+ }
16633
+ }
16634
+ if (oldestKey) {
16635
+ const entry = pool.get(oldestKey);
16636
+ if (entry) {
16637
+ clearTimeout(entry.timer);
16638
+ try {
16639
+ entry.adapter.close();
16640
+ } catch {}
16641
+ pool.delete(oldestKey);
16642
+ }
16643
+ }
16644
+ }
16645
+ function scheduleEviction(key, entry) {
16646
+ clearTimeout(entry.timer);
16647
+ entry.timer = setTimeout(() => {
16648
+ const current = pool.get(key);
16649
+ if (current === entry) {
16650
+ try {
16651
+ current.adapter.close();
16652
+ } catch {}
16653
+ pool.delete(key);
16654
+ }
16655
+ }, IDLE_TIMEOUT_MS);
16656
+ }
16657
+ async function getConnection(resolvedPath) {
16658
+ if (!factory) {
16659
+ throw new Error("No adapter factory configured");
16660
+ }
16661
+ const existing = pool.get(resolvedPath);
16662
+ if (existing) {
16663
+ existing.lastUsed = Date.now();
16664
+ scheduleEviction(resolvedPath, existing);
16665
+ return existing.adapter;
16666
+ }
16667
+ if (pool.size >= MAX_CONNECTIONS) {
16668
+ evictOldest();
16669
+ }
16670
+ const adapter = await factory.open(resolvedPath);
16671
+ const entry = {
16672
+ adapter,
16673
+ path: resolvedPath,
16674
+ lastUsed: Date.now(),
16675
+ timer: setTimeout(() => {
16676
+ return;
16677
+ }, 0)
16678
+ };
16679
+ pool.set(resolvedPath, entry);
16680
+ scheduleEviction(resolvedPath, entry);
16681
+ return adapter;
16682
+ }
16683
+ function closeConnection(resolvedPath) {
16684
+ const entry = pool.get(resolvedPath);
16685
+ if (!entry)
16686
+ return false;
16687
+ clearTimeout(entry.timer);
16688
+ try {
16689
+ entry.adapter.close();
16690
+ } catch {}
16691
+ pool.delete(resolvedPath);
16692
+ return true;
16693
+ }
16694
+ var MAX_CONNECTIONS = 8, IDLE_TIMEOUT_MS, pool, factory = null;
16695
+ var init_connection_pool = __esm(() => {
16696
+ IDLE_TIMEOUT_MS = 5 * 60 * 1000;
16697
+ pool = new Map;
16698
+ });
16699
+
16700
+ // web-src/server/database/connections-store.ts
16701
+ import { randomUUID } from "node:crypto";
16702
+ import { chmod } from "node:fs/promises";
16703
+ import { join as join11 } from "node:path";
16704
+ function secretKey(cwd, id) {
16705
+ return `${cwd}\x00${id}`;
16706
+ }
16707
+ function extractSecrets(value) {
16708
+ return {
16709
+ ...typeof value.user === "string" ? { user: value.user } : {},
16710
+ ...typeof value.username === "string" ? { username: value.username } : {},
16711
+ ...typeof value.accessKeyId === "string" ? { accessKeyId: value.accessKeyId } : {},
16712
+ ...typeof value.password === "string" ? { password: value.password } : {},
16713
+ ...typeof value.secretAccessKey === "string" ? { secretAccessKey: value.secretAccessKey } : {},
16714
+ ...typeof value.sessionToken === "string" ? { sessionToken: value.sessionToken } : {}
16715
+ };
16716
+ }
16717
+ function withRuntimeSecrets(cwd, connection) {
16718
+ return {
16719
+ ...connection,
16720
+ ...runtimeSecrets.get(secretKey(cwd, connection.id)) ?? {}
16721
+ };
16722
+ }
16723
+ function connectionsFilePath(root) {
16724
+ return join11(root, ".code-viewer", CONNECTIONS_FILE_NAME);
16725
+ }
16726
+ function emptyState() {
16727
+ return { version: 1, connections: [] };
16728
+ }
16729
+ function requiredString(value, maxLength = MAX_VALUE_LENGTH) {
16730
+ return typeof value === "string" && value.length <= maxLength ? value : "";
16731
+ }
16732
+ function optionalString2(value, maxLength = MAX_VALUE_LENGTH) {
16733
+ const normalized = requiredString(value, maxLength);
16734
+ return normalized || undefined;
16735
+ }
16736
+ function validPort(value) {
16737
+ const port = Number(value);
16738
+ return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
16739
+ }
16740
+ function validEndpoint(value) {
16741
+ const raw = requiredString(value, MAX_VALUE_LENGTH);
16742
+ if (!raw)
16743
+ return null;
16744
+ try {
16745
+ const url = new URL(raw);
16746
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password) {
16747
+ return null;
16748
+ }
16749
+ return url.toString().replace(/\/$/, "");
16750
+ } catch {
16751
+ return null;
16752
+ }
16753
+ }
16754
+ function sanitizeConnection(raw) {
16755
+ if (!raw || typeof raw !== "object")
16756
+ return null;
16757
+ const input = raw;
16758
+ const id = requiredString(input.id, 80);
16759
+ const name = requiredString(input.name, MAX_NAME_LENGTH).trim();
16760
+ if (!/^connection:[a-f0-9-]{16,64}$/.test(id) || !name)
16761
+ return null;
16762
+ const base = { id, name };
16763
+ if (input.kind === "postgresql" || input.kind === "mysql") {
16764
+ const host = requiredString(input.host, MAX_HOST_LENGTH).trim();
16765
+ const port = validPort(input.port);
16766
+ const user = requiredString(input.user).trim();
16767
+ const database = requiredString(input.database).trim();
16768
+ if (!host || !port || !database)
16769
+ return null;
16770
+ return {
16771
+ ...base,
16772
+ kind: input.kind,
16773
+ host,
16774
+ port,
16775
+ user,
16776
+ password: requiredString(input.password),
16777
+ database,
16778
+ ...optionalString2(input.schema)?.trim() ? { schema: optionalString2(input.schema)?.trim() } : {},
16779
+ tls: input.tls === true
16780
+ };
16781
+ }
16782
+ if (input.kind === "redis") {
16783
+ const host = requiredString(input.host, MAX_HOST_LENGTH).trim();
16784
+ const port = validPort(input.port);
16785
+ if (!host || !port)
16786
+ return null;
16787
+ return {
16788
+ ...base,
16789
+ kind: "redis",
16790
+ host,
16791
+ port,
16792
+ ...optionalString2(input.username)?.trim() ? { username: optionalString2(input.username)?.trim() } : {},
16793
+ password: requiredString(input.password),
16794
+ tls: input.tls === true
16795
+ };
16796
+ }
16797
+ if (input.kind === "elasticsearch") {
16798
+ const endpoint = validEndpoint(input.endpoint);
16799
+ if (!endpoint)
16800
+ return null;
16801
+ return {
16802
+ ...base,
16803
+ kind: "elasticsearch",
16804
+ endpoint,
16805
+ ...optionalString2(input.username)?.trim() ? { username: optionalString2(input.username)?.trim() } : {},
16806
+ password: requiredString(input.password)
16807
+ };
16808
+ }
16809
+ if (input.kind === "s3" || input.kind === "dynamodb") {
16810
+ const endpoint = validEndpoint(input.endpoint);
16811
+ const region = requiredString(input.region).trim();
16812
+ const accessKeyId = requiredString(input.accessKeyId).trim();
16813
+ if (!endpoint || !region)
16814
+ return null;
16815
+ return {
16816
+ ...base,
16817
+ kind: input.kind,
16818
+ endpoint,
16819
+ region,
16820
+ accessKeyId,
16821
+ secretAccessKey: requiredString(input.secretAccessKey),
16822
+ ...optionalString2(input.sessionToken) ? { sessionToken: optionalString2(input.sessionToken) } : {}
16823
+ };
16824
+ }
16825
+ return null;
16826
+ }
16827
+ function validateDatastoreConnection(raw, fallbackId = "connection:0000000000000000") {
16828
+ const input = raw && typeof raw === "object" ? raw : {};
16829
+ const connection = sanitizeConnection({
16830
+ ...input,
16831
+ id: typeof input.id === "string" && input.id ? input.id : fallbackId
16832
+ });
16833
+ if (!connection || (connection.kind === "postgresql" || connection.kind === "mysql") && !connection.user || (connection.kind === "s3" || connection.kind === "dynamodb") && !connection.accessKeyId) {
16834
+ throw new Error("invalid datastore connection");
16835
+ }
16836
+ return connection;
16837
+ }
16838
+ function sanitizeState(raw) {
16839
+ if (!raw || typeof raw !== "object")
16840
+ return emptyState();
16841
+ const input = raw;
16842
+ if (input.version !== 1 || !Array.isArray(input.connections)) {
16843
+ return emptyState();
16844
+ }
16845
+ const seen = new Set;
16846
+ const connections = [];
16847
+ for (const candidate of input.connections) {
16848
+ if (connections.length >= MAX_CONNECTIONS2)
16849
+ break;
16850
+ const connection = sanitizeConnection(candidate);
16851
+ if (!connection || seen.has(connection.id))
16852
+ continue;
16853
+ seen.add(connection.id);
16854
+ connections.push(connection);
16855
+ }
16856
+ return { version: 1, connections };
16857
+ }
16858
+ async function protectFile(cwd) {
16859
+ await chmod(connectionsFilePath(cwd), 384).catch(() => {
16860
+ return;
16861
+ });
16862
+ }
16863
+ async function loadDatastoreConnections(cwd) {
16864
+ return (await store.load(cwd)).connections.map((connection) => withRuntimeSecrets(cwd, connection));
16865
+ }
16866
+ async function findDatastoreConnection(cwd, id) {
16867
+ return (await loadDatastoreConnections(cwd)).find((entry) => entry.id === id) ?? null;
16868
+ }
16869
+ async function saveDatastoreConnection(cwd, raw) {
16870
+ const input = raw && typeof raw === "object" ? raw : {};
16871
+ const requestedId = typeof input.id === "string" ? input.id : "";
16872
+ const id = requestedId || `connection:${randomUUID()}`;
16873
+ const result = await store.update(cwd, (state) => {
16874
+ const storedExisting = state.connections.find((entry) => entry.id === id);
16875
+ const existing = storedExisting ? withRuntimeSecrets(cwd, storedExisting) : undefined;
16876
+ const merged = sanitizeConnection({
16877
+ ...existing ?? {},
16878
+ ...input,
16879
+ id,
16880
+ password: input.password === undefined && existing && "password" in existing ? existing.password : input.password,
16881
+ secretAccessKey: input.secretAccessKey === undefined && existing && "secretAccessKey" in existing ? existing.secretAccessKey : input.secretAccessKey,
16882
+ sessionToken: input.sessionToken === undefined && existing && "sessionToken" in existing ? existing.sessionToken : input.sessionToken
16883
+ });
16884
+ if (!merged || (merged.kind === "postgresql" || merged.kind === "mysql") && !merged.user || (merged.kind === "s3" || merged.kind === "dynamodb") && !merged.accessKeyId) {
16885
+ throw new Error("invalid datastore connection");
16886
+ }
16887
+ const connections = state.connections.filter((entry) => entry.id !== id);
16888
+ if (!storedExisting && connections.length >= MAX_CONNECTIONS2) {
16889
+ throw new Error("too many datastore connections");
16890
+ }
16891
+ connections.push(merged);
16892
+ return {
16893
+ state: { version: 1, connections },
16894
+ result: merged
16895
+ };
16896
+ });
16897
+ runtimeSecrets.set(secretKey(cwd, result.id), extractSecrets(result));
16898
+ await protectFile(cwd);
16899
+ return result;
16900
+ }
16901
+ async function deleteDatastoreConnection(cwd, id) {
16902
+ const deleted = await store.update(cwd, (state) => {
16903
+ const connections = state.connections.filter((entry) => entry.id !== id);
16904
+ return {
16905
+ state: { version: 1, connections },
16906
+ result: connections.length !== state.connections.length
16907
+ };
16908
+ });
16909
+ runtimeSecrets.delete(secretKey(cwd, id));
16910
+ await protectFile(cwd);
16911
+ return deleted;
16912
+ }
16913
+ function connectionToFileInfo(connection) {
16914
+ return {
16915
+ id: connection.id,
16916
+ path: "saved connection",
16917
+ name: connection.name,
16918
+ sizeBytes: 0,
16919
+ kind: connection.kind,
16920
+ savedConnection: true
16921
+ };
16922
+ }
16923
+ function publicConnection(connection) {
16924
+ const {
16925
+ password: _password,
16926
+ user: _user,
16927
+ username: _username,
16928
+ accessKeyId: _accessKeyId,
16929
+ ...withoutPassword
16930
+ } = connection;
16931
+ const {
16932
+ secretAccessKey: _secret,
16933
+ sessionToken: _token,
16934
+ ...safe
16935
+ } = withoutPassword;
16936
+ return safe;
16937
+ }
16938
+ var CONNECTIONS_FILE_NAME = "datastore-connections.json", MAX_CONNECTIONS2 = 64, MAX_JSON_BYTES, MAX_NAME_LENGTH = 120, MAX_HOST_LENGTH = 253, MAX_VALUE_LENGTH = 4096, runtimeSecrets, store;
16939
+ var init_connections_store = __esm(() => {
16940
+ init_json_store();
16941
+ MAX_JSON_BYTES = 256 * 1024;
16942
+ runtimeSecrets = new Map;
16943
+ store = createJsonFileStore({
16944
+ filePath: connectionsFilePath,
16945
+ empty: emptyState,
16946
+ sanitize: sanitizeState,
16947
+ maxBytes: MAX_JSON_BYTES,
16948
+ backupSuffix: "bak",
16949
+ sizeErrorMessage: "datastore connections state too large",
16950
+ serialize: (state) => `${JSON.stringify({
16951
+ version: 1,
16952
+ connections: state.connections.map(publicConnection)
16953
+ }, null, 2)}
16954
+ `
16955
+ });
16956
+ });
16957
+
16958
+ // web-src/server/database/global-search.ts
16959
+ function isTextLikeType(type) {
16960
+ const upper = type.toUpperCase();
16961
+ return upper.includes("CHAR") || upper.includes("TEXT") || upper.includes("VARCHAR") || upper.includes("CLOB") || upper.includes("STRING") || upper === "JSON" || upper === "JSONB" || upper === "XML" || upper === "UUID";
16962
+ }
16963
+ function escapeLikeTerm(term) {
16964
+ return term.replace(/=/g, "==").replace(/%/g, "=%").replace(/_/g, "=_");
16965
+ }
16966
+ async function searchTableAsync(adapter, table, columns, term, maxHits, includeNonText, pkColumns, signal) {
16967
+ const kind = adapter.kind;
16968
+ const searchCols = includeNonText ? columns.filter((c) => c.type.toUpperCase() !== "BLOB" && c.type.toUpperCase() !== "BYTEA") : columns.filter((c) => isTextLikeType(c.type));
16969
+ if (searchCols.length === 0)
16970
+ return [];
16971
+ const escapedTerm = escapeLikeTerm(term);
16972
+ const tbl = sanitizeIdentifier(table, kind);
16973
+ const hits = [];
16974
+ const db = asAsync(adapter);
16975
+ for (const col of searchCols) {
16976
+ if (signal?.aborted)
16977
+ break;
16978
+ if (hits.length >= maxHits)
16979
+ break;
16980
+ const colId = sanitizeIdentifier(col.name, kind);
16981
+ const castCol = kind === "mysql" ? `CAST(${colId} AS CHAR)` : `CAST(${colId} AS TEXT)`;
16982
+ let sql;
16983
+ const remaining = maxHits - hits.length;
16984
+ if (kind === "sqlite") {
16985
+ sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ? ESCAPE '='`;
16986
+ } else {
16987
+ const likeVal = escapeSqlString(`%${escapedTerm}%`);
16988
+ sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ${likeVal} ESCAPE '='`;
16989
+ }
16990
+ try {
16991
+ const params = kind === "sqlite" ? [`%${escapedTerm}%`] : undefined;
16992
+ const result = await db.readonlyQuery(sql, params, remaining, signal);
16993
+ for (const row of result.rows) {
16994
+ const colIdx = result.columns.indexOf(col.name);
16995
+ const valueRaw = colIdx >= 0 ? serializeDbValue(row[colIdx]) : null;
16996
+ const valueStr = valueRaw == null ? "" : String(valueRaw);
16997
+ const preview = valueStr.length > 200 ? `${valueStr.slice(0, 200)}...` : valueStr;
16998
+ let rowKeyJson;
16999
+ if (pkColumns.length > 0) {
17000
+ const keyObj = {};
17001
+ for (const pk of pkColumns) {
17002
+ const pkIdx = result.columns.indexOf(pk);
17003
+ if (pkIdx >= 0)
17004
+ keyObj[pk] = serializeDbValue(row[pkIdx]);
17005
+ }
17006
+ rowKeyJson = JSON.stringify(keyObj);
17007
+ }
17008
+ hits.push({
17009
+ table,
17010
+ column: col.name,
17011
+ rowKeyJson,
17012
+ valuePreview: preview,
17013
+ rowPreview: serializeDbRow(row)
17014
+ });
17015
+ }
17016
+ } catch (err) {
17017
+ if (isAbortLikeError(err, signal))
17018
+ throw err;
17019
+ }
17020
+ }
17021
+ return hits;
17022
+ }
17023
+ function getPrimaryKeyColumnsFromColumns(columns) {
17024
+ return columns.filter((c) => c.primaryKey).map((c) => c.name);
17025
+ }
17026
+ var init_global_search = __esm(() => {
17027
+ init_serialize();
17028
+ init_sql_utils();
17029
+ });
17030
+
16396
17031
  // web-src/server/database/handle-shared.ts
16397
17032
  function createDockerAdapterCache(maxEntries = DEFAULT_MAX_DOCKER_ADAPTER_CACHE, idleMs = DEFAULT_DOCKER_ADAPTER_IDLE_MS) {
16398
17033
  const cache = new Map;
@@ -16642,6 +17277,24 @@ async function resolveDockerExplorerAsync(cwd, dbParam, kind, cache, openFn, omi
16642
17277
  }
16643
17278
  return { dbId: dbParam, explorer };
16644
17279
  }
17280
+ async function resolveDatastoreExplorerAsync(cwd, dbParam, kind, cache, openDocker, openSaved, omitDirNames, signal) {
17281
+ if (dbParam?.startsWith("connection:")) {
17282
+ const connection = await findDatastoreConnection(cwd, dbParam);
17283
+ if (!connection || connection.kind !== kind) {
17284
+ return textError(`${kind} connection not found`, 404);
17285
+ }
17286
+ try {
17287
+ const explorer = await waitForCallerAbort(cache.getOrOpenAsync(dbParam, () => openSaved(connection)), signal, `${kind} open aborted`);
17288
+ return { dbId: dbParam, explorer };
17289
+ } catch (err) {
17290
+ if (isAbortLikeError(err, signal)) {
17291
+ return textError(`${kind} open aborted`, 503);
17292
+ }
17293
+ throw err;
17294
+ }
17295
+ }
17296
+ return resolveDockerExplorerAsync(cwd, dbParam, kind, cache, openDocker, omitDirNames, signal);
17297
+ }
16645
17298
  async function dispatchRoutes(req, url, routes, sideEffectAllowed, wrap = (res) => res, handleRouteError) {
16646
17299
  if (!Object.prototype.hasOwnProperty.call(routes, url.pathname))
16647
17300
  return null;
@@ -16688,6 +17341,7 @@ function handleError(prefix, action, err, signal) {
16688
17341
  var DEFAULT_MAX_DOCKER_ADAPTER_CACHE = 8, DEFAULT_DOCKER_ADAPTER_IDLE_MS, MAX_LOGGED_ERROR_BODY = 500, logQueue;
16689
17342
  var init_handle_shared = __esm(() => {
16690
17343
  init_docker_utils();
17344
+ init_connections_store();
16691
17345
  init_discovery();
16692
17346
  DEFAULT_DOCKER_ADAPTER_IDLE_MS = 5 * 60 * 1000;
16693
17347
  logQueue = Promise.resolve();
@@ -16703,7 +17357,12 @@ function closeDynamoDbAdapter(dbId) {
16703
17357
  dynamoDbAdapterCache.close(dbId);
16704
17358
  }
16705
17359
  function resolveDynamoDb(cwd, dbParam, signal, omitDirNames) {
16706
- return resolveDockerExplorerAsync(cwd, dbParam, "dynamodb", dynamoDbAdapterCache, (info) => openDynamoDbExplorerAsync(info), omitDirNames, signal);
17360
+ return resolveDatastoreExplorerAsync(cwd, dbParam, "dynamodb", dynamoDbAdapterCache, (info) => openDynamoDbExplorerAsync(info), (connection) => {
17361
+ if (connection.kind !== "dynamodb") {
17362
+ throw new Error("invalid DynamoDB connection");
17363
+ }
17364
+ return createDynamoDbAdapter(connection);
17365
+ }, omitDirNames, signal);
16707
17366
  }
16708
17367
  function dynamoDbErrorResponse(err, action, signal) {
16709
17368
  if (isAbortLikeError(err, signal)) {
@@ -16944,7 +17603,12 @@ function closeElasticsearchAdapter(dbId) {
16944
17603
  esAdapterCache.close(dbId);
16945
17604
  }
16946
17605
  function resolveEs(cwd, dbParam, signal, omitDirNames) {
16947
- return resolveDockerExplorerAsync(cwd, dbParam, "elasticsearch", esAdapterCache, (info) => openElasticsearchAdapterAsync(info.serviceName, info.env, info.composeDir), omitDirNames, signal);
17606
+ return resolveDatastoreExplorerAsync(cwd, dbParam, "elasticsearch", esAdapterCache, (info) => openElasticsearchAdapterAsync(info.serviceName, info.env, info.composeDir), (connection) => {
17607
+ if (connection.kind !== "elasticsearch") {
17608
+ throw new Error("invalid Elasticsearch connection");
17609
+ }
17610
+ return createElasticsearchAdapter(connection);
17611
+ }, omitDirNames, signal);
16948
17612
  }
16949
17613
  async function handleIndices(req, cwd, url, omitDirNames) {
16950
17614
  const r = await resolveEs(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
@@ -17189,7 +17853,11 @@ function closeRedisAdapter(dbId) {
17189
17853
  redisAdapterCache.close(dbId);
17190
17854
  }
17191
17855
  function resolveRedis(cwd, dbParam, signal, omitDirNames) {
17192
- return resolveDockerExplorerAsync(cwd, dbParam, "redis", redisAdapterCache, (info) => openRedisExplorerAsync(info.serviceName, info.env, info.composeDir), omitDirNames, signal);
17856
+ return resolveDatastoreExplorerAsync(cwd, dbParam, "redis", redisAdapterCache, (info) => openRedisExplorerAsync(info.serviceName, info.env, info.composeDir), (connection) => {
17857
+ if (connection.kind !== "redis")
17858
+ throw new Error("invalid Redis connection");
17859
+ return createRedisAdapter(connection);
17860
+ }, omitDirNames, signal);
17193
17861
  }
17194
17862
  async function handleDatabases(req, cwd, url, omitDirNames) {
17195
17863
  const r = await resolveRedis(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
@@ -17381,7 +18049,11 @@ function closeS3Adapter(dbId) {
17381
18049
  s3AdapterCache.close(dbId);
17382
18050
  }
17383
18051
  function resolveS3(cwd, dbParam, signal, omitDirNames) {
17384
- return resolveDockerExplorerAsync(cwd, dbParam, "s3", s3AdapterCache, (info) => openS3ExplorerAsync(info), omitDirNames, signal);
18052
+ return resolveDatastoreExplorerAsync(cwd, dbParam, "s3", s3AdapterCache, (info) => openS3ExplorerAsync(info), (connection) => {
18053
+ if (connection.kind !== "s3")
18054
+ throw new Error("invalid S3 connection");
18055
+ return createS3Adapter(connection);
18056
+ }, omitDirNames, signal);
17385
18057
  }
17386
18058
  function validateBucket(value) {
17387
18059
  if (!value)
@@ -17831,11 +18503,11 @@ var init_handle_s3 = __esm(() => {
17831
18503
  });
17832
18504
 
17833
18505
  // web-src/server/database/query-history.ts
17834
- import { join as join11 } from "node:path";
18506
+ import { join as join12 } from "node:path";
17835
18507
  function historyFilePath(root) {
17836
- return join11(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
18508
+ return join12(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
17837
18509
  }
17838
- function emptyState() {
18510
+ function emptyState2() {
17839
18511
  return { version: 1, entries: [] };
17840
18512
  }
17841
18513
  function serializeHistoryState(state) {
@@ -17845,14 +18517,14 @@ function serializeHistoryState(state) {
17845
18517
  };
17846
18518
  let content = `${JSON.stringify(normalized, null, 2)}
17847
18519
  `;
17848
- while (normalized.entries.length > 1 && Buffer.byteLength(content, "utf8") > MAX_JSON_BYTES) {
18520
+ while (normalized.entries.length > 1 && Buffer.byteLength(content, "utf8") > MAX_JSON_BYTES2) {
17849
18521
  normalized.entries.pop();
17850
18522
  content = `${JSON.stringify(normalized, null, 2)}
17851
18523
  `;
17852
18524
  }
17853
18525
  return content;
17854
18526
  }
17855
- function optionalString2(value, maxLen) {
18527
+ function optionalString3(value, maxLen) {
17856
18528
  if (typeof value !== "string")
17857
18529
  return;
17858
18530
  if (!value || value.length > maxLen || value.includes("\x00"))
@@ -17883,16 +18555,16 @@ function sanitizeEntry(raw) {
17883
18555
  if (!raw || typeof raw !== "object")
17884
18556
  return null;
17885
18557
  const entry = raw;
17886
- const id = optionalString2(entry.id, MAX_ID_LEN);
17887
- const dbId = optionalString2(entry.dbId, MAX_DB_ID_LEN);
17888
- const sql = optionalString2(entry.sql, MAX_SQL_LEN);
18558
+ const id = optionalString3(entry.id, MAX_ID_LEN);
18559
+ const dbId = optionalString3(entry.dbId, MAX_DB_ID_LEN);
18560
+ const sql = optionalString3(entry.sql, MAX_SQL_LEN);
17889
18561
  if (!id || !dbId || !sql)
17890
18562
  return null;
17891
18563
  const columns = Array.isArray(entry.columns) ? entry.columns.filter((col) => typeof col === "string" && !!col).map((col) => col.slice(0, MAX_COLUMN_LEN)).slice(0, MAX_COLUMNS) : [];
17892
18564
  const rowsPreview = sanitizeRows(entry.rowsPreview);
17893
- const schema = optionalString2(entry.schema, MAX_SCHEMA_LEN);
17894
- const title = optionalString2(entry.title, MAX_TEXT_LEN);
17895
- const body = optionalString2(entry.body, MAX_TEXT_LEN);
18565
+ const schema = optionalString3(entry.schema, MAX_SCHEMA_LEN);
18566
+ const title = optionalString3(entry.title, MAX_TEXT_LEN);
18567
+ const body = optionalString3(entry.body, MAX_TEXT_LEN);
17896
18568
  return {
17897
18569
  id,
17898
18570
  dbId,
@@ -17906,17 +18578,17 @@ function sanitizeEntry(raw) {
17906
18578
  savedRows: finiteNumber(entry.savedRows) ?? rowsPreview.length,
17907
18579
  truncated: typeof entry.truncated === "boolean" ? entry.truncated : false,
17908
18580
  elapsedMs: finiteNumber(entry.elapsedMs) ?? 0,
17909
- executedAt: optionalString2(entry.executedAt, 64) ?? new Date(0).toISOString(),
18581
+ executedAt: optionalString3(entry.executedAt, 64) ?? new Date(0).toISOString(),
17910
18582
  executedBy: entry.executedBy === "ai" ? "ai" : "user",
17911
18583
  source: entry.source === "cli" ? "cli" : "browser"
17912
18584
  };
17913
18585
  }
17914
18586
  function sanitizeHistoryState(raw) {
17915
18587
  if (!raw || typeof raw !== "object")
17916
- return emptyState();
18588
+ return emptyState2();
17917
18589
  const entriesRaw = raw.entries;
17918
18590
  if (!Array.isArray(entriesRaw))
17919
- return emptyState();
18591
+ return emptyState2();
17920
18592
  const entries = [];
17921
18593
  for (const entry of entriesRaw) {
17922
18594
  if (entries.length >= MAX_ENTRIES2)
@@ -17955,7 +18627,7 @@ function deleteQueryHistoryEntry(state, id) {
17955
18627
  }
17956
18628
  function clearQueryHistory(state, dbId, schema) {
17957
18629
  if (!dbId)
17958
- return emptyState();
18630
+ return emptyState2();
17959
18631
  return {
17960
18632
  version: 1,
17961
18633
  entries: state.entries.filter((e) => {
@@ -17967,14 +18639,14 @@ function clearQueryHistory(state, dbId, schema) {
17967
18639
  })
17968
18640
  };
17969
18641
  }
17970
- var CODE_VIEWER_DIR3 = ".code-viewer", HISTORY_FILE_NAME = "query-history.json", MAX_ENTRIES2 = 200, MAX_PREVIEW_ROWS = 100, MAX_JSON_BYTES = 1e6, MAX_ID_LEN = 128, MAX_DB_ID_LEN = 2048, MAX_SCHEMA_LEN = 512, MAX_SQL_LEN = 64000, MAX_TEXT_LEN = 64000, MAX_COLUMN_LEN = 512, MAX_COLUMNS = 500, historyStore;
18642
+ var CODE_VIEWER_DIR3 = ".code-viewer", HISTORY_FILE_NAME = "query-history.json", MAX_ENTRIES2 = 200, MAX_PREVIEW_ROWS = 100, MAX_JSON_BYTES2 = 1e6, MAX_ID_LEN = 128, MAX_DB_ID_LEN = 2048, MAX_SCHEMA_LEN = 512, MAX_SQL_LEN = 64000, MAX_TEXT_LEN = 64000, MAX_COLUMN_LEN = 512, MAX_COLUMNS = 500, historyStore;
17971
18643
  var init_query_history = __esm(() => {
17972
18644
  init_json_store();
17973
18645
  historyStore = createJsonFileStore({
17974
18646
  filePath: historyFilePath,
17975
- empty: emptyState,
18647
+ empty: emptyState2,
17976
18648
  sanitize: sanitizeHistoryState,
17977
- maxBytes: MAX_JSON_BYTES,
18649
+ maxBytes: MAX_JSON_BYTES2,
17978
18650
  backupSuffix: "corrupt",
17979
18651
  serialize: serializeHistoryState
17980
18652
  });
@@ -17983,9 +18655,9 @@ var init_query_history = __esm(() => {
17983
18655
  // web-src/server/database/snapshot-store.ts
17984
18656
  import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
17985
18657
  import { mkdirSync as mkdirSync3 } from "node:fs";
17986
- import { join as join12 } from "node:path";
18658
+ import { join as join13 } from "node:path";
17987
18659
  async function getStoreDb(cwd) {
17988
- const dbPath = join12(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
18660
+ const dbPath = join13(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
17989
18661
  if (storeDb && storeDbPath === dbPath)
17990
18662
  return storeDb;
17991
18663
  if (storeDb) {
@@ -17993,7 +18665,7 @@ async function getStoreDb(cwd) {
17993
18665
  storeDb.close();
17994
18666
  } catch {}
17995
18667
  }
17996
- mkdirSync3(join12(cwd, CODE_VIEWER_DIR4), { recursive: true });
18668
+ mkdirSync3(join13(cwd, CODE_VIEWER_DIR4), { recursive: true });
17997
18669
  const DbClass = await loadSqliteClass();
17998
18670
  storeDb = new DbClass(dbPath);
17999
18671
  storeDbPath = dbPath;
@@ -18599,11 +19271,11 @@ var init_snapshot_runner = __esm(() => {
18599
19271
  });
18600
19272
 
18601
19273
  // web-src/server/database/tabs-store.ts
18602
- import { join as join13 } from "node:path";
19274
+ import { join as join14 } from "node:path";
18603
19275
  function tabsFilePath(root) {
18604
- return join13(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
19276
+ return join14(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
18605
19277
  }
18606
- function emptyState2() {
19278
+ function emptyState3() {
18607
19279
  return { version: 1, tabs: [], activeTabId: null };
18608
19280
  }
18609
19281
  function isValidCssSize(s) {
@@ -18725,10 +19397,10 @@ function sanitizeDynamodb(v) {
18725
19397
  }
18726
19398
  function sanitize(input) {
18727
19399
  if (!input || typeof input !== "object")
18728
- return emptyState2();
19400
+ return emptyState3();
18729
19401
  const obj = input;
18730
19402
  if (obj.version !== 1)
18731
- return emptyState2();
19403
+ return emptyState3();
18732
19404
  const rawTabs = Array.isArray(obj.tabs) ? obj.tabs : [];
18733
19405
  const seenIds = new Set;
18734
19406
  const tabs = [];
@@ -18796,7 +19468,7 @@ async function loadTabsAsync(cwd) {
18796
19468
  async function saveTabsAsync(cwd, state) {
18797
19469
  return tabsStore.save(cwd, state);
18798
19470
  }
18799
- var CODE_VIEWER_DIR5 = ".code-viewer", TABS_FILE_NAME = "tabs.json", MAX_TABS = 64, MAX_JSON_BYTES2 = 1e6, MAX_SQL_DRAFT_LEN = 16000, MAX_ES_QUERY_LEN = 16000, MAX_TAB_ID_LEN = 128, MAX_DB_ID_LEN2 = 2048, MAX_SCHEMA_NAME_LEN = 512, MAX_TABLE_NAME_LEN2 = 512, MAX_REDIS_KEY_LEN = 1024, MAX_REDIS_KEY_FILTER_LEN = 512, MAX_INDEX_NAME_LEN = 256, MAX_S3_BUCKET_LEN = 256, MAX_S3_KEY_LEN = 2048, MAX_S3_QUERY_LEN = 2048, MAX_DYNAMODB_TABLE_LEN = 255, MAX_DYNAMODB_EXPRESSION_LEN = 4096, MAX_DYNAMODB_ATTRIBUTE_VALUES_LEN = 16000, MAX_DYNAMODB_ITEM_KEY_LEN = 4096, MAX_CSS_SIZE_LEN = 16, VALID_VIEWS, tabsStore;
19471
+ var CODE_VIEWER_DIR5 = ".code-viewer", TABS_FILE_NAME = "tabs.json", MAX_TABS = 64, MAX_JSON_BYTES3 = 1e6, MAX_SQL_DRAFT_LEN = 16000, MAX_ES_QUERY_LEN = 16000, MAX_TAB_ID_LEN = 128, MAX_DB_ID_LEN2 = 2048, MAX_SCHEMA_NAME_LEN = 512, MAX_TABLE_NAME_LEN2 = 512, MAX_REDIS_KEY_LEN = 1024, MAX_REDIS_KEY_FILTER_LEN = 512, MAX_INDEX_NAME_LEN = 256, MAX_S3_BUCKET_LEN = 256, MAX_S3_KEY_LEN = 2048, MAX_S3_QUERY_LEN = 2048, MAX_DYNAMODB_TABLE_LEN = 255, MAX_DYNAMODB_EXPRESSION_LEN = 4096, MAX_DYNAMODB_ATTRIBUTE_VALUES_LEN = 16000, MAX_DYNAMODB_ITEM_KEY_LEN = 4096, MAX_CSS_SIZE_LEN = 16, VALID_VIEWS, tabsStore;
18800
19472
  var init_tabs_store = __esm(() => {
18801
19473
  init_json_store();
18802
19474
  VALID_VIEWS = new Set([
@@ -18809,9 +19481,9 @@ var init_tabs_store = __esm(() => {
18809
19481
  ]);
18810
19482
  tabsStore = createJsonFileStore({
18811
19483
  filePath: tabsFilePath,
18812
- empty: emptyState2,
19484
+ empty: emptyState3,
18813
19485
  sanitize,
18814
- maxBytes: MAX_JSON_BYTES2,
19486
+ maxBytes: MAX_JSON_BYTES3,
18815
19487
  backupSuffix: "bak",
18816
19488
  sizeErrorMessage: "tabs state too large"
18817
19489
  });
@@ -18839,6 +19511,10 @@ function ensureInit() {
18839
19511
  initialized = true;
18840
19512
  }
18841
19513
  async function getAdapter(r, _cwd, signal) {
19514
+ if (r.saved) {
19515
+ const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
19516
+ return dockerAdapterCache.getOrOpenAsync(cacheKey, () => createSqlCliAdapter({ ...r.saved, schema: r.schema }));
19517
+ }
18842
19518
  if (r.docker) {
18843
19519
  const docker = r.docker;
18844
19520
  const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
@@ -18886,6 +19562,24 @@ async function resolveSupabaseSchema(info, requestedSchema, signal) {
18886
19562
  async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
18887
19563
  if (!dbParam)
18888
19564
  return textError("missing db parameter", 400);
19565
+ if (dbParam.startsWith("connection:")) {
19566
+ const connection = await findDatastoreConnection(cwd, dbParam);
19567
+ if (!connection)
19568
+ return textError("datastore connection not found", 404);
19569
+ if (connection.kind !== "postgresql" && connection.kind !== "mysql") {
19570
+ return textError(`${connection.kind} must use its datastore routes`, 400);
19571
+ }
19572
+ const requestedSchema = normalizeSchemaParam(schemaParam);
19573
+ if (requestedSchema instanceof Response)
19574
+ return requestedSchema;
19575
+ const schema = connection.kind === "postgresql" ? requestedSchema || connection.schema || "public" : undefined;
19576
+ return {
19577
+ resolved: dbParam,
19578
+ dbId: dbParam,
19579
+ saved: connection,
19580
+ ...schema ? { schema } : {}
19581
+ };
19582
+ }
18889
19583
  if (dbParam.startsWith("supabase:")) {
18890
19584
  const parsed = parseSupabaseDbId(dbParam);
18891
19585
  if (!parsed)
@@ -18993,10 +19687,11 @@ async function expandDockerServicesForFiles(dockerServices, listDockerDatabases,
18993
19687
  }
18994
19688
  async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_DB_FILE_DISCOVERY_DEPS) {
18995
19689
  ensureInit();
18996
- const [sqliteSettled, dockerSettled, supabaseSettled] = await Promise.allSettled([
19690
+ const [sqliteSettled, dockerSettled, supabaseSettled, connectionsSettled] = await Promise.allSettled([
18997
19691
  deps.discoverSqliteFiles(cwd, omitDirNames, signal),
18998
19692
  deps.discoverDockerDatabases(cwd, omitDirNames, signal),
18999
- deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal)
19693
+ deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal),
19694
+ (deps.loadConnections ?? loadDatastoreConnections)(cwd)
19000
19695
  ]);
19001
19696
  if (sqliteSettled.status === "rejected") {
19002
19697
  throw sqliteSettled.reason;
@@ -19004,11 +19699,15 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
19004
19699
  if (supabaseSettled.status === "rejected") {
19005
19700
  throw supabaseSettled.reason;
19006
19701
  }
19702
+ if (connectionsSettled.status === "rejected") {
19703
+ throw connectionsSettled.reason;
19704
+ }
19007
19705
  if (dockerSettled.status === "rejected" && isAbortLikeError(dockerSettled.reason, signal)) {
19008
19706
  throw dockerSettled.reason;
19009
19707
  }
19010
19708
  const sqliteFiles = sqliteSettled.value;
19011
19709
  const supabaseProjects = supabaseSettled.value;
19710
+ const savedConnections = connectionsSettled.value;
19012
19711
  const dockerServices = dockerSettled.status === "fulfilled" ? dockerSettled.value : [];
19013
19712
  const dockerErrors = [];
19014
19713
  if (dockerSettled.status === "rejected") {
@@ -19027,7 +19726,8 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
19027
19726
  kind: "sqlite"
19028
19727
  })),
19029
19728
  ...dockerEntries.map(toFileInfo),
19030
- ...supabaseProjects.map(toFileInfo)
19729
+ ...supabaseProjects.map(toFileInfo),
19730
+ ...savedConnections.map(connectionToFileInfo)
19031
19731
  ],
19032
19732
  ...dockerTruncated ? { truncated: true } : {},
19033
19733
  ...dockerErrors.length > 0 ? { dockerError: dockerErrors.join("; ") } : {}
@@ -19052,6 +19752,26 @@ async function createDbSchemasResponse(cwd, dbParam, schemaParam, omitDirNames,
19052
19752
  };
19053
19753
  return { ok: true, value: body2 };
19054
19754
  }
19755
+ if (r.saved?.kind === "postgresql") {
19756
+ try {
19757
+ const adapter = await getAdapter(r, cwd, signal);
19758
+ const { result, executedSql: executedSql2 } = await captureSql(() => adapter.executeReadonlyQueryAsync("SELECT schema_name FROM information_schema.schemata ORDER BY schema_name", undefined, 1000, signal));
19759
+ return {
19760
+ ok: true,
19761
+ value: {
19762
+ dbId: r.dbId,
19763
+ schemas: result.rows.map((row) => ({ name: String(row[0]) })),
19764
+ selectedSchema: r.schema,
19765
+ executedSql: executedSql2
19766
+ }
19767
+ };
19768
+ } catch (err) {
19769
+ return {
19770
+ ok: false,
19771
+ response: handleError("database", "list schemas", err, signal)
19772
+ };
19773
+ }
19774
+ }
19055
19775
  if (!r.docker || r.docker.kind !== "postgresql") {
19056
19776
  const body2 = { dbId: r.dbId, schemas: [] };
19057
19777
  return { ok: true, value: body2 };
@@ -20054,6 +20774,118 @@ async function handleTabsPut(cwd, req) {
20054
20774
  async function handleDbUiGet(cwd) {
20055
20775
  return jsonLoadResponse(() => loadDbUiState(cwd), "db UI", "failed to load db UI state");
20056
20776
  }
20777
+ function closeSavedConnection(id, kind) {
20778
+ if (kind === "postgresql" || kind === "mysql") {
20779
+ dockerAdapterCache.close(id);
20780
+ dockerAdapterCache.closePrefix(`${id}\x00`);
20781
+ return;
20782
+ }
20783
+ DOCKER_CLOSE_REGISTRY[kind]?.(id);
20784
+ }
20785
+ async function handleConnections(cwd, req) {
20786
+ if (req.method === "GET") {
20787
+ const connections = await loadDatastoreConnections(cwd);
20788
+ return json({ connections: connections.map(publicConnection) });
20789
+ }
20790
+ const body = await parseBoundedJsonBody(req, 65536, "connection payload too large");
20791
+ if (body instanceof Response)
20792
+ return body;
20793
+ if (req.method === "PUT") {
20794
+ try {
20795
+ const connection = await saveDatastoreConnection(cwd, body);
20796
+ closeSavedConnection(connection.id, connection.kind);
20797
+ return json({ connection: publicConnection(connection) });
20798
+ } catch (err) {
20799
+ const message = err instanceof Error ? err.message : "invalid datastore connection";
20800
+ return textError(message, message === "too many datastore connections" ? 409 : 400);
20801
+ }
20802
+ }
20803
+ const id = body && typeof body === "object" && "id" in body ? body.id : undefined;
20804
+ if (typeof id !== "string" || !id.startsWith("connection:")) {
20805
+ return textError("invalid datastore connection id", 400);
20806
+ }
20807
+ const existing = await findDatastoreConnection(cwd, id);
20808
+ if (!existing)
20809
+ return textError("datastore connection not found", 404);
20810
+ await deleteDatastoreConnection(cwd, id);
20811
+ closeSavedConnection(id, existing.kind);
20812
+ return json({ ok: true });
20813
+ }
20814
+ async function probeDatastoreConnection(connection, signal) {
20815
+ if (connection.kind === "postgresql" || connection.kind === "mysql") {
20816
+ const adapter2 = createSqlCliAdapter(connection);
20817
+ try {
20818
+ await adapter2.getTablesAsync(signal);
20819
+ } finally {
20820
+ adapter2.close();
20821
+ }
20822
+ return;
20823
+ }
20824
+ if (connection.kind === "redis") {
20825
+ const adapter2 = createRedisAdapter(connection);
20826
+ try {
20827
+ await adapter2.listDatabasesAsync(signal);
20828
+ } finally {
20829
+ adapter2.close();
20830
+ }
20831
+ return;
20832
+ }
20833
+ if (connection.kind === "elasticsearch") {
20834
+ const adapter2 = createElasticsearchAdapter(connection);
20835
+ try {
20836
+ await adapter2.listIndicesAsync(signal);
20837
+ } finally {
20838
+ adapter2.close();
20839
+ }
20840
+ return;
20841
+ }
20842
+ if (connection.kind === "s3") {
20843
+ const adapter2 = createS3Adapter(connection);
20844
+ try {
20845
+ await adapter2.listBuckets(signal);
20846
+ } finally {
20847
+ adapter2.close();
20848
+ }
20849
+ return;
20850
+ }
20851
+ if (connection.kind !== "dynamodb") {
20852
+ throw new Error("invalid datastore connection");
20853
+ }
20854
+ const adapter = createDynamoDbAdapter(connection);
20855
+ try {
20856
+ await adapter.listTablesAsync({ limit: 1, signal });
20857
+ } finally {
20858
+ adapter.close();
20859
+ }
20860
+ }
20861
+ async function handleConnectionTest(cwd, req) {
20862
+ const body = await parseBoundedJsonBody(req, 65536, "connection payload too large");
20863
+ if (body instanceof Response)
20864
+ return body;
20865
+ const input = body && typeof body === "object" ? body : {};
20866
+ let existing = null;
20867
+ if (typeof input.id === "string") {
20868
+ existing = await findDatastoreConnection(cwd, input.id);
20869
+ if (!existing)
20870
+ return textError("datastore connection not found", 404);
20871
+ }
20872
+ try {
20873
+ const connection = validateDatastoreConnection({
20874
+ ...existing ?? {},
20875
+ ...input
20876
+ });
20877
+ await probeDatastoreConnection(connection, req.signal);
20878
+ return json({ ok: true });
20879
+ } catch (err) {
20880
+ if (isAbortLikeError(err, req.signal)) {
20881
+ return textError("connection test aborted", 503);
20882
+ }
20883
+ if (err instanceof Error && err.message === "invalid datastore connection") {
20884
+ return textError(err.message, 400);
20885
+ }
20886
+ return textError("connection failed", 400);
20887
+ }
20888
+ }
20057
20889
  async function handleDbUiPatch(cwd, req) {
20058
20890
  const body = await parseBoundedJsonBody(req, MAX_DB_UI_BODY_BYTES, "db UI body too large");
20059
20891
  if (body instanceof Response)
@@ -20074,6 +20906,12 @@ async function handleClose(cwd, req, omitDirNames) {
20074
20906
  return body;
20075
20907
  if (!body.db)
20076
20908
  return textError("missing db", 400);
20909
+ if (body.db.startsWith("connection:")) {
20910
+ const connection = await findDatastoreConnection(cwd, body.db);
20911
+ if (connection)
20912
+ closeSavedConnection(body.db, connection.kind);
20913
+ return json({ ok: true });
20914
+ }
20077
20915
  if (body.db.startsWith("docker:")) {
20078
20916
  const parsed = parseDockerDbId(body.db);
20079
20917
  if (!parsed)
@@ -20174,6 +21012,16 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
20174
21012
  methods: ["GET"],
20175
21013
  handler: () => handleFiles(cwd, omitDirNames, req.signal)
20176
21014
  },
21015
+ "/_db/connections": {
21016
+ methods: ["GET", "PUT", "DELETE"],
21017
+ sideEffect: (requestMethod) => requestMethod !== "GET",
21018
+ handler: () => handleConnections(cwd, req)
21019
+ },
21020
+ "/_db/connections/test": {
21021
+ methods: ["POST"],
21022
+ sideEffect: true,
21023
+ handler: () => handleConnectionTest(cwd, req)
21024
+ },
20177
21025
  "/_db/schemas": {
20178
21026
  methods: ["GET"],
20179
21027
  handler: () => handleSchemas(cwd, url, omitDirNames, req.signal)
@@ -20294,9 +21142,14 @@ var init_handle = __esm(() => {
20294
21142
  init_state_store();
20295
21143
  init_docker();
20296
21144
  init_docker_utils();
21145
+ init_dynamodb();
21146
+ init_elasticsearch();
21147
+ init_redis();
21148
+ init_s3();
20297
21149
  init_sql_capture();
20298
21150
  init_sqlite();
20299
21151
  init_connection_pool();
21152
+ init_connections_store();
20300
21153
  init_discovery();
20301
21154
  init_global_search();
20302
21155
  init_handle_dynamodb();
@@ -20314,7 +21167,8 @@ var init_handle = __esm(() => {
20314
21167
  discoverSqliteFiles: discoverSqliteFilesAsync,
20315
21168
  discoverDockerDatabases: discoverDockerDatabasesAsync,
20316
21169
  listDockerDatabases: listDockerDatabasesAsync,
20317
- discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync
21170
+ discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync,
21171
+ loadConnections: loadDatastoreConnections
20318
21172
  };
20319
21173
  searchJobs = new Map;
20320
21174
  snapshotJobs = new Map;
@@ -20356,7 +21210,7 @@ var init_handle = __esm(() => {
20356
21210
 
20357
21211
  // web-src/server/doctor.ts
20358
21212
  import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
20359
- import { dirname as dirname4, join as join14, relative as relative5 } from "node:path";
21213
+ import { dirname as dirname4, join as join15, relative as relative5 } from "node:path";
20360
21214
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20361
21215
  function statusWorse(a, b) {
20362
21216
  const rank = { ok: 0, warn: 1, error: 2 };
@@ -20450,7 +21304,7 @@ function findCodeViewerPackageJson() {
20450
21304
  cursor = dirname4(process.argv[1] || ".");
20451
21305
  }
20452
21306
  for (let depth = 0;depth < 8; depth += 1) {
20453
- const candidate = join14(cursor, "package.json");
21307
+ const candidate = join15(cursor, "package.json");
20454
21308
  try {
20455
21309
  const raw = readFileSync6(candidate, "utf8");
20456
21310
  const pkg = JSON.parse(raw);
@@ -20546,7 +21400,7 @@ async function checkSqlite(cwd) {
20546
21400
  return { id: "sqlite", title: "SQLite driver", rows };
20547
21401
  }
20548
21402
  async function trySnapshotDbOpen(cwd) {
20549
- const dbPath = join14(cwd, SNAPSHOT_DB_REL);
21403
+ const dbPath = join15(cwd, SNAPSHOT_DB_REL);
20550
21404
  try {
20551
21405
  statSync5(dbPath);
20552
21406
  } catch {
@@ -20567,7 +21421,7 @@ async function trySnapshotDbOpen(cwd) {
20567
21421
  }
20568
21422
  }
20569
21423
  function checkSnapshotStore(cwd) {
20570
- const dbPath = join14(cwd, SNAPSHOT_DB_REL);
21424
+ const dbPath = join15(cwd, SNAPSHOT_DB_REL);
20571
21425
  const dir = dirname4(dbPath);
20572
21426
  let dirStatus = "ok";
20573
21427
  let dirDetail = dir;
@@ -21571,7 +22425,7 @@ function normalizeNewDirectoryName(name) {
21571
22425
 
21572
22426
  // web-src/server/cache.ts
21573
22427
  import { lstatSync as lstatSync3 } from "node:fs";
21574
- import { join as join15 } from "node:path";
22428
+ import { join as join16 } from "node:path";
21575
22429
  function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
21576
22430
  return !!cached && now - cached.storedAt <= ttlMs;
21577
22431
  }
@@ -21586,7 +22440,7 @@ function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MA
21586
22440
  }
21587
22441
  function worktreeFileSignature(path, cwd) {
21588
22442
  try {
21589
- const stats = lstatSync3(join15(cwd, path));
22443
+ const stats = lstatSync3(join16(cwd, path));
21590
22444
  const inode = "ino" in stats ? stats.ino : 0;
21591
22445
  return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
21592
22446
  } catch {
@@ -21632,12 +22486,12 @@ function startDevAssetReload(options) {
21632
22486
  var init_dev_assets = () => {};
21633
22487
 
21634
22488
  // web-src/server/journal.ts
21635
- import { join as join16 } from "node:path";
22489
+ import { join as join17 } from "node:path";
21636
22490
  function dailyJournalFilePath(root) {
21637
- return join16(root, CODE_VIEWER_DIR, DAILY_JOURNAL_FILE_NAME);
22491
+ return join17(root, CODE_VIEWER_DIR, DAILY_JOURNAL_FILE_NAME);
21638
22492
  }
21639
22493
  function journalTasksFilePath(root) {
21640
- return join16(root, CODE_VIEWER_DIR, JOURNAL_TASKS_FILE_NAME);
22494
+ return join17(root, CODE_VIEWER_DIR, JOURNAL_TASKS_FILE_NAME);
21641
22495
  }
21642
22496
  function emptyDailyJournalState() {
21643
22497
  return { version: 1, entries: [] };
@@ -21650,7 +22504,7 @@ function makeJournalId(prefix) {
21650
22504
  const time = Date.now().toString(36);
21651
22505
  return `${prefix}-${time}${random}`;
21652
22506
  }
21653
- function optionalString3(value, maxLen) {
22507
+ function optionalString4(value, maxLen) {
21654
22508
  if (typeof value !== "string")
21655
22509
  return;
21656
22510
  if (value.includes("\x00"))
@@ -21676,12 +22530,12 @@ function normalizeJournalEntry(raw) {
21676
22530
  if (!raw || typeof raw !== "object")
21677
22531
  return null;
21678
22532
  const entry = raw;
21679
- const id = optionalString3(entry.id, 128);
22533
+ const id = optionalString4(entry.id, 128);
21680
22534
  const date = isIsoDate(entry.date) ? entry.date : undefined;
21681
22535
  const body = optionalBody(entry.body, JOURNAL_ENTRY_BODY_MAX_BYTES);
21682
22536
  if (!id || !date || body === undefined)
21683
22537
  return null;
21684
- const title = optionalString3(entry.title, JOURNAL_TITLE_MAX_CHARS);
22538
+ const title = optionalString4(entry.title, JOURNAL_TITLE_MAX_CHARS);
21685
22539
  return {
21686
22540
  id,
21687
22541
  date,
@@ -21689,8 +22543,8 @@ function normalizeJournalEntry(raw) {
21689
22543
  body,
21690
22544
  labels: normalizeJournalLabels(entry.labels),
21691
22545
  source: normalizeSource(entry.source),
21692
- created_at: optionalString3(entry.created_at, 64) ?? new Date(0).toISOString(),
21693
- updated_at: optionalString3(entry.updated_at, 64) ?? new Date(0).toISOString()
22546
+ created_at: optionalString4(entry.created_at, 64) ?? new Date(0).toISOString(),
22547
+ updated_at: optionalString4(entry.updated_at, 64) ?? new Date(0).toISOString()
21694
22548
  };
21695
22549
  }
21696
22550
  function normalizeDailyJournalState(raw) {
@@ -21714,13 +22568,13 @@ function normalizeTaskNote(raw) {
21714
22568
  if (!raw || typeof raw !== "object")
21715
22569
  return null;
21716
22570
  const note = raw;
21717
- const id = optionalString3(note.id, 128);
22571
+ const id = optionalString4(note.id, 128);
21718
22572
  const body = optionalBody(note.body, JOURNAL_TASK_NOTE_MAX_BYTES);
21719
22573
  if (!id || body === undefined)
21720
22574
  return null;
21721
22575
  return {
21722
22576
  id,
21723
- at: optionalString3(note.at, 64) ?? new Date(0).toISOString(),
22577
+ at: optionalString4(note.at, 64) ?? new Date(0).toISOString(),
21724
22578
  body,
21725
22579
  source: normalizeSource(note.source)
21726
22580
  };
@@ -21729,9 +22583,9 @@ function normalizeTaskClaim(raw) {
21729
22583
  if (!raw || typeof raw !== "object")
21730
22584
  return;
21731
22585
  const claim = raw;
21732
- const by = optionalString3(claim.by, 128);
21733
- const claimedAt = optionalString3(claim.claimed_at, 64);
21734
- const leaseExpiresAt = optionalString3(claim.lease_expires_at, 64);
22586
+ const by = optionalString4(claim.by, 128);
22587
+ const claimedAt = optionalString4(claim.claimed_at, 64);
22588
+ const leaseExpiresAt = optionalString4(claim.lease_expires_at, 64);
21735
22589
  if (!by || !claimedAt || !leaseExpiresAt)
21736
22590
  return;
21737
22591
  return {
@@ -21744,8 +22598,8 @@ function normalizeJournalTask(raw) {
21744
22598
  if (!raw || typeof raw !== "object")
21745
22599
  return null;
21746
22600
  const task = raw;
21747
- const id = optionalString3(task.id, 128);
21748
- const title = optionalString3(task.title, JOURNAL_TITLE_MAX_CHARS);
22601
+ const id = optionalString4(task.id, 128);
22602
+ const title = optionalString4(task.title, JOURNAL_TITLE_MAX_CHARS);
21749
22603
  if (!id || !title)
21750
22604
  return null;
21751
22605
  const status = isJournalTaskStatus(task.status) ? task.status : "todo";
@@ -21753,8 +22607,8 @@ function normalizeJournalTask(raw) {
21753
22607
  const body = optionalBody(task.body, JOURNAL_TASK_BODY_MAX_BYTES) ?? "";
21754
22608
  const dueDate = isIsoDate(task.due_date) ? task.due_date : undefined;
21755
22609
  const sourceDate = isIsoDate(task.source_date) ? task.source_date : undefined;
21756
- const journalEntryId = optionalString3(task.journal_entry_id, 128);
21757
- const completedAt = optionalString3(task.completed_at, 64);
22610
+ const journalEntryId = optionalString4(task.journal_entry_id, 128);
22611
+ const completedAt = optionalString4(task.completed_at, 64);
21758
22612
  const notes = Array.isArray(task.notes) ? task.notes.slice(0, MAX_NOTES_PER_TASK).map(normalizeTaskNote).filter((note) => note !== null) : [];
21759
22613
  const claim = normalizeTaskClaim(task.claim);
21760
22614
  return {
@@ -21764,8 +22618,8 @@ function normalizeJournalTask(raw) {
21764
22618
  status,
21765
22619
  priority,
21766
22620
  labels: normalizeJournalLabels(task.labels),
21767
- created_at: optionalString3(task.created_at, 64) ?? new Date(0).toISOString(),
21768
- updated_at: optionalString3(task.updated_at, 64) ?? new Date(0).toISOString(),
22621
+ created_at: optionalString4(task.created_at, 64) ?? new Date(0).toISOString(),
22622
+ updated_at: optionalString4(task.updated_at, 64) ?? new Date(0).toISOString(),
21769
22623
  ...dueDate ? { due_date: dueDate } : {},
21770
22624
  ...sourceDate ? { source_date: sourceDate } : {},
21771
22625
  ...journalEntryId ? { journal_entry_id: journalEntryId } : {},
@@ -21846,7 +22700,7 @@ function addDailyJournalEntry(state, input, now, makeId3 = makeJournalId) {
21846
22700
  const valid = validateEntryInput(input);
21847
22701
  if (valid.ok === false)
21848
22702
  return valid;
21849
- const title = optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS);
22703
+ const title = optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS);
21850
22704
  const entry = {
21851
22705
  id: makeId3("j"),
21852
22706
  date: input.date,
@@ -21877,7 +22731,7 @@ function updateDailyJournalEntry(state, id, patch, now) {
21877
22731
  next.date = patch.date;
21878
22732
  }
21879
22733
  if (patch.title !== undefined) {
21880
- const title = optionalString3(patch.title, JOURNAL_TITLE_MAX_CHARS);
22734
+ const title = optionalString4(patch.title, JOURNAL_TITLE_MAX_CHARS);
21881
22735
  if (title)
21882
22736
  next.title = title;
21883
22737
  else
@@ -21910,7 +22764,7 @@ function deleteDailyJournalEntry(state, id) {
21910
22764
  };
21911
22765
  }
21912
22766
  function validateTaskInput(input) {
21913
- if (!optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS))
22767
+ if (!optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS))
21914
22768
  return { ok: false, error: "title is required" };
21915
22769
  if (input.body !== undefined && optionalBody(input.body, JOURNAL_TASK_BODY_MAX_BYTES) === undefined)
21916
22770
  return { ok: false, error: "body is too large" };
@@ -21955,7 +22809,7 @@ function addJournalTask(state, input, now, makeId3 = makeJournalId) {
21955
22809
  const status = input.status || anchor?.status || "todo";
21956
22810
  const task = {
21957
22811
  id: makeId3("t"),
21958
- title: optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS) || "Untitled task",
22812
+ title: optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS) || "Untitled task",
21959
22813
  body: input.body || "",
21960
22814
  status,
21961
22815
  priority: input.priority || "p2",
@@ -21980,7 +22834,7 @@ function updateJournalTask(state, id, patch, now) {
21980
22834
  return { ok: false, error: "task not found" };
21981
22835
  const next = { ...task, updated_at: now };
21982
22836
  if (patch.title !== undefined) {
21983
- const title = optionalString3(patch.title, JOURNAL_TITLE_MAX_CHARS);
22837
+ const title = optionalString4(patch.title, JOURNAL_TITLE_MAX_CHARS);
21984
22838
  if (!title)
21985
22839
  return { ok: false, error: "title is required" };
21986
22840
  next.title = title;
@@ -22022,7 +22876,7 @@ function updateJournalTask(state, id, patch, now) {
22022
22876
  return { ok: false, error: "source date must be YYYY-MM-DD" };
22023
22877
  }
22024
22878
  if (patch.journal_entry_id !== undefined) {
22025
- const journalEntryId = optionalString3(patch.journal_entry_id, 128);
22879
+ const journalEntryId = optionalString4(patch.journal_entry_id, 128);
22026
22880
  if (journalEntryId)
22027
22881
  next.journal_entry_id = journalEntryId;
22028
22882
  else
@@ -22072,10 +22926,10 @@ function linkGithubIssueTask(state, input, now, makeId3 = makeJournalId) {
22072
22926
  if (!Number.isInteger(input.issue_number) || input.issue_number < 1) {
22073
22927
  return { ok: false, error: "issue number must be a positive integer" };
22074
22928
  }
22075
- const title = optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS) || `GitHub issue #${input.issue_number}`;
22076
- const repo = optionalString3(input.repo, 120);
22077
- const issueUrl = optionalString3(input.url, 240);
22078
- const memoLabel = optionalString3(input.memo_label, 80);
22929
+ const title = optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS) || `GitHub issue #${input.issue_number}`;
22930
+ const repo = optionalString4(input.repo, 120);
22931
+ const issueUrl = optionalString4(input.url, 240);
22932
+ const memoLabel = optionalString4(input.memo_label, 80);
22079
22933
  const requiredLabels = githubIssueRequiredLabels(input.issue_number, repo, input.labels);
22080
22934
  const linkLabel = journalIssueLabel(input.issue_number);
22081
22935
  const repoLabel = journalIssueRepoLabel(repo);
@@ -22153,7 +23007,7 @@ function claimJournalTask(state, id, input, now) {
22153
23007
  ok: false,
22154
23008
  error: "only todo or expired doing tasks can be claimed"
22155
23009
  };
22156
- const by = optionalString3(input.by, 128) || "ai";
23010
+ const by = optionalString4(input.by, 128) || "ai";
22157
23011
  const wipLimit = input.wip_limit;
22158
23012
  if (wipLimit !== undefined && wipLimit > 0) {
22159
23013
  const activeDoing = state.tasks.filter((item) => {
@@ -22198,7 +23052,7 @@ function completeJournalTask(state, id, input, now, makeId3 = makeJournalId) {
22198
23052
  const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
22199
23053
  if (!activeClaim)
22200
23054
  return { ok: false, error: "task must be claimed before completion" };
22201
- const by = optionalString3(input.by, 128);
23055
+ const by = optionalString4(input.by, 128);
22202
23056
  if (!by)
22203
23057
  return { ok: false, error: "task completion requires claim owner" };
22204
23058
  if (task.claim?.by !== by)
@@ -22270,7 +23124,7 @@ var init_journal2 = __esm(() => {
22270
23124
 
22271
23125
  // web-src/server/search-service.ts
22272
23126
  import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync5 } from "node:fs";
22273
- import { join as join17, relative as relative6 } from "node:path";
23127
+ import { join as join18, relative as relative6 } from "node:path";
22274
23128
  async function rgAvailableAsync(cwd) {
22275
23129
  if (rgAvailableCache !== null)
22276
23130
  return rgAvailableCache;
@@ -22299,7 +23153,7 @@ function safeWorktreePath(env, path) {
22299
23153
  return null;
22300
23154
  if (isGitInternalPath(path))
22301
23155
  return null;
22302
- const full = join17(env.cwd, path);
23156
+ const full = join18(env.cwd, path);
22303
23157
  if (!existsSync7(full))
22304
23158
  return null;
22305
23159
  let realCwd;
@@ -22491,7 +23345,7 @@ var init_search_service = __esm(() => {
22491
23345
 
22492
23346
  // web-src/server/mcp.ts
22493
23347
  import { readFileSync as readFileSync8 } from "node:fs";
22494
- import { join as join18 } from "node:path";
23348
+ import { join as join19 } from "node:path";
22495
23349
  function defaultMcpTools(options = {}) {
22496
23350
  return [
22497
23351
  {
@@ -23965,7 +24819,7 @@ var init_mcp = __esm(() => {
23965
24819
  init_search_cli();
23966
24820
  init_search_service();
23967
24821
  init_status_cli();
23968
- PACKAGE_VERSION = JSON.parse(readFileSync8(join18(ROOT, "package.json"), "utf8")).version;
24822
+ PACKAGE_VERSION = JSON.parse(readFileSync8(join19(ROOT, "package.json"), "utf8")).version;
23969
24823
  MCP_SERVER_INFO = {
23970
24824
  name: "code-viewer",
23971
24825
  title: "code-viewer",
@@ -24061,7 +24915,7 @@ import {
24061
24915
  writeFileSync as writeFileSync2
24062
24916
  } from "node:fs";
24063
24917
  import { homedir as homedir3 } from "node:os";
24064
- import { basename as basename3, dirname as dirname5, extname as extname2, join as join19, relative as relative7 } from "node:path";
24918
+ import { basename as basename3, dirname as dirname5, extname as extname2, join as join20, relative as relative7 } from "node:path";
24065
24919
  function parseCli() {
24066
24920
  const rest = [];
24067
24921
  for (let i = 2;i < process.argv.length; i++) {
@@ -24183,7 +25037,7 @@ Examples:
24183
25037
  }
24184
25038
  function warnIfLegacyConfigPresent() {
24185
25039
  try {
24186
- if (existsSync8(join19(cwd, ".code-viewer.json"))) {
25040
+ if (existsSync8(join20(cwd, ".code-viewer.json"))) {
24187
25041
  console.warn("[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed.");
24188
25042
  }
24189
25043
  } catch {}
@@ -24290,7 +25144,7 @@ function staticFile(pathname) {
24290
25144
  const spec = map[pathname];
24291
25145
  if (!spec)
24292
25146
  return null;
24293
- const full = join19(WEB_ROOT, spec[0]);
25147
+ const full = join20(WEB_ROOT, spec[0]);
24294
25148
  if (!existsSync8(full))
24295
25149
  return text("not found", 404);
24296
25150
  return new Response(readFileSync9(full), {
@@ -24541,7 +25395,7 @@ function safeWorktreePath2(path) {
24541
25395
  return safeWorktreePath(currentSearchEnv(), path);
24542
25396
  }
24543
25397
  function worktreePath(path) {
24544
- return join19(cwd, path);
25398
+ return join20(cwd, path);
24545
25399
  }
24546
25400
  function safeOpenWorktreePath(path) {
24547
25401
  if (path === "") {
@@ -24830,7 +25684,7 @@ async function handleLog(url) {
24830
25684
  }
24831
25685
  function blamePathKey(p) {
24832
25686
  try {
24833
- const st = statSync6(join19(cwd, p));
25687
+ const st = statSync6(join20(cwd, p));
24834
25688
  return `${st.mtimeMs}:${st.size}`;
24835
25689
  } catch {
24836
25690
  return "missing";
@@ -25363,7 +26217,7 @@ async function handleUploadFiles(req) {
25363
26217
  total += file.size;
25364
26218
  if (total > MAX_UPLOAD_TOTAL_BYTES)
25365
26219
  return text("upload too large", 413);
25366
- const target = join19(realDir, safeName);
26220
+ const target = join20(realDir, safeName);
25367
26221
  if (relative7(realDir, dirname5(target)) !== "")
25368
26222
  return text("invalid filename", 400);
25369
26223
  if (existsSync8(target))
@@ -25485,9 +26339,9 @@ function triggerUpdate(changedPaths) {
25485
26339
  sendSse("update", data);
25486
26340
  }
25487
26341
  function moveMacPathIntoTrash(path) {
25488
- const trashDir = join19(homedir3(), ".Trash");
26342
+ const trashDir = join20(homedir3(), ".Trash");
25489
26343
  const base = basename3(path) || "code-viewer-trash-item";
25490
- const target = join19(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
26344
+ const target = join20(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
25491
26345
  try {
25492
26346
  mkdirSync4(trashDir, { recursive: true });
25493
26347
  renameSync(path, target);
@@ -25529,7 +26383,7 @@ async function restoreTrashPath(originalPath, trashPath) {
25529
26383
  if (!existsSync8(trashPath))
25530
26384
  return { ok: false, error: "trash item not found" };
25531
26385
  try {
25532
- const trashRoot = join19(homedir3(), ".Trash");
26386
+ const trashRoot = join20(homedir3(), ".Trash");
25533
26387
  const trashRelative = relative7(trashRoot, trashPath);
25534
26388
  if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
25535
26389
  return { ok: false, error: "invalid trash handle" };
@@ -25679,7 +26533,7 @@ async function handleCreateDirectory(req) {
25679
26533
  const targetPath = dir ? `${dir}/${name}` : name;
25680
26534
  if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
25681
26535
  return text("invalid target", 400);
25682
- const target = join19(parent, name);
26536
+ const target = join20(parent, name);
25683
26537
  if (existsSync8(target))
25684
26538
  return text("already exists", 409);
25685
26539
  try {
@@ -26277,8 +27131,8 @@ var init_preview = __esm(async () => {
26277
27131
  init_server_registry();
26278
27132
  init_state_store();
26279
27133
  init_worktree_watcher();
26280
- WEB_ROOT = join19(ROOT, "web");
26281
- VERSION = JSON.parse(readFileSync9(join19(ROOT, "package.json"), "utf8")).version;
27134
+ WEB_ROOT = join20(ROOT, "web");
27135
+ VERSION = JSON.parse(readFileSync9(join20(ROOT, "package.json"), "utf8")).version;
26282
27136
  DEFAULT_ARGS = ["HEAD"];
26283
27137
  WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
26284
27138
  LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;