@youtyan/code-viewer 0.8.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1127,7 +1127,7 @@ var init_name_pattern = __esm(() => {
1127
1127
 
1128
1128
  // web-src/server/runtime.ts
1129
1129
  import { spawn, spawnSync } from "node:child_process";
1130
- import { createReadStream, promises as fs } from "node:fs";
1130
+ import { createReadStream, promises as fs, statSync as statSync2 } from "node:fs";
1131
1131
  import {
1132
1132
  createServer
1133
1133
  } from "node:http";
@@ -1278,10 +1278,20 @@ function appendProcessError(stderr, err) {
1278
1278
  return `${stderr}${stderr ? `
1279
1279
  ` : ""}${err.message}`;
1280
1280
  }
1281
+ function assertReadableRegularFile(path) {
1282
+ const stats = statSync2(path);
1283
+ if (stats.isFile())
1284
+ return;
1285
+ const error = new Error(`not a regular file: ${path}`);
1286
+ error.code = stats.isDirectory() ? "EISDIR" : "EINVAL";
1287
+ throw error;
1288
+ }
1281
1289
  function fileReadableStream(path) {
1290
+ assertReadableRegularFile(path);
1282
1291
  return Readable.toWeb(createReadStream(path));
1283
1292
  }
1284
1293
  function fileByteRangeResponseBody(path, start, endInclusive) {
1294
+ assertReadableRegularFile(path);
1285
1295
  return Readable.toWeb(createReadStream(path, { start, end: endInclusive }));
1286
1296
  }
1287
1297
  async function readFileTextRange(path, start, endExclusive) {
@@ -1415,7 +1425,7 @@ import {
1415
1425
  readFileSync,
1416
1426
  readlinkSync,
1417
1427
  realpathSync as realpathSync2,
1418
- statSync as statSync2
1428
+ statSync as statSync3
1419
1429
  } from "node:fs";
1420
1430
  import { open, stat } from "node:fs/promises";
1421
1431
  import { dirname as dirname3, join as join4, posix, relative as relative2 } from "node:path";
@@ -2060,7 +2070,7 @@ function isGitInternalPath(path) {
2060
2070
  function syntheticUncommittedBlameFromWorktree(cwd, path) {
2061
2071
  const filePath = join4(cwd, path);
2062
2072
  try {
2063
- const stat2 = statSync2(filePath);
2073
+ const stat2 = statSync3(filePath);
2064
2074
  if (!stat2.isFile())
2065
2075
  return { lines: [], commits: {}, error: "not a file" };
2066
2076
  const text = readFileSync(filePath, "utf8");
@@ -2250,7 +2260,7 @@ function resolveWorktreeSymlinkTarget(cwd, full) {
2250
2260
  let symlink_target_type = "missing";
2251
2261
  if (realpathWithinRepo(cwd, full, false) !== null) {
2252
2262
  try {
2253
- const stat2 = statSync2(full);
2263
+ const stat2 = statSync3(full);
2254
2264
  symlink_target_type = stat2.isDirectory() ? "tree" : stat2.isFile() ? "blob" : "missing";
2255
2265
  } catch {
2256
2266
  symlink_target_type = "missing";
@@ -3982,7 +3992,7 @@ __export(exports_file_cli, {
3982
3992
  FILE_DEFAULT_HISTORY_LIMIT: () => FILE_DEFAULT_HISTORY_LIMIT,
3983
3993
  FILE_AGENT_HELP: () => FILE_AGENT_HELP
3984
3994
  });
3985
- import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync4, statSync as statSync3 } from "node:fs";
3995
+ import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
3986
3996
  import { join as join6, relative as relative3 } from "node:path";
3987
3997
  function validatePath(value) {
3988
3998
  return validateRepoRelativePathValue(value, "--path");
@@ -4381,7 +4391,7 @@ async function readShowTextAsync(root, command) {
4381
4391
  };
4382
4392
  }
4383
4393
  try {
4384
- const stat2 = statSync3(full);
4394
+ const stat2 = statSync4(full);
4385
4395
  if (!stat2.isFile()) {
4386
4396
  return { code: 1, stdout: "", stderr: "not a file" };
4387
4397
  }
@@ -10531,6 +10541,38 @@ var init_sql_capture = __esm(() => {
10531
10541
  storage = new AsyncLocalStorage;
10532
10542
  });
10533
10543
 
10544
+ // web-src/server/database/adapters/table-meta-cache.ts
10545
+ function createTableMetaCache(now = () => Date.now()) {
10546
+ const columns = new Map;
10547
+ const rowCounts = new Map;
10548
+ async function readThrough(store, table, ttlMs, load) {
10549
+ const cached = store.get(table);
10550
+ if (cached && cached.expires > now())
10551
+ return cached.value;
10552
+ const value = await load();
10553
+ store.set(table, { value, expires: now() + ttlMs });
10554
+ return value;
10555
+ }
10556
+ return {
10557
+ getColumns(table, load) {
10558
+ return readThrough(columns, table, COLUMNS_TTL_MS, load);
10559
+ },
10560
+ getRowCount(table, load) {
10561
+ return readThrough(rowCounts, table, ROWCOUNT_TTL_MS, load);
10562
+ },
10563
+ invalidate(table) {
10564
+ if (table) {
10565
+ columns.delete(table);
10566
+ rowCounts.delete(table);
10567
+ return;
10568
+ }
10569
+ columns.clear();
10570
+ rowCounts.clear();
10571
+ }
10572
+ };
10573
+ }
10574
+ var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000;
10575
+
10534
10576
  // web-src/server/database/adapters/docker.ts
10535
10577
  import { spawnSync as spawnSync3 } from "node:child_process";
10536
10578
  import mysql from "mysql2/promise";
@@ -10966,39 +11008,6 @@ function buildTableSelectList(columns, kind) {
10966
11008
  });
10967
11009
  return hasSpatialColumn ? parts.join(", ") : "*";
10968
11010
  }
10969
- function createTableMetaCache(now = () => Date.now()) {
10970
- const columns = new Map;
10971
- const rowCounts = new Map;
10972
- return {
10973
- async getColumns(table, fetch2) {
10974
- const cached = columns.get(table);
10975
- const current = now();
10976
- if (cached && cached.expires > current)
10977
- return cached.value;
10978
- const value = await fetch2();
10979
- columns.set(table, { value, expires: now() + COLUMNS_TTL_MS });
10980
- return value;
10981
- },
10982
- async getRowCount(table, fetch2) {
10983
- const cached = rowCounts.get(table);
10984
- const current = now();
10985
- if (cached && cached.expires > current)
10986
- return cached.value;
10987
- const value = await fetch2();
10988
- rowCounts.set(table, { value, expires: now() + ROWCOUNT_TTL_MS });
10989
- return value;
10990
- },
10991
- invalidate(table) {
10992
- if (table) {
10993
- columns.delete(table);
10994
- rowCounts.delete(table);
10995
- return;
10996
- }
10997
- columns.clear();
10998
- rowCounts.clear();
10999
- }
11000
- };
11001
- }
11002
11011
  function observeBackgroundRejection(promise) {
11003
11012
  promise.catch(() => {
11004
11013
  return;
@@ -11618,7 +11627,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
11618
11627
  ...kind === "postgresql" && schema ? { schema } : {}
11619
11628
  });
11620
11629
  }
11621
- 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";
11630
+ var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config), 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";
11622
11631
  var init_docker = __esm(() => {
11623
11632
  init_mutate();
11624
11633
  init_sql_snapshot();
@@ -13860,6 +13869,72 @@ var init_s3 = __esm(() => {
13860
13869
  s3DockerCurlTimeoutMs = DEFAULT_S3_DOCKER_CURL_TIMEOUT_MS;
13861
13870
  });
13862
13871
 
13872
+ // web-src/server/database/adapters/sqlite-introspection.ts
13873
+ function sqliteTableInfoSql(table) {
13874
+ return `PRAGMA table_info(${sanitizeIdentifier(table)})`;
13875
+ }
13876
+ function sqliteIndexListSql(table) {
13877
+ return `PRAGMA index_list(${sanitizeIdentifier(table)})`;
13878
+ }
13879
+ function sqliteIndexInfoSql(index) {
13880
+ return `PRAGMA index_info(${sanitizeIdentifier(index)})`;
13881
+ }
13882
+ function sqliteForeignKeyListSql(table) {
13883
+ return `PRAGMA foreign_key_list(${sanitizeIdentifier(table)})`;
13884
+ }
13885
+ function sqliteColumnFromPragmaRow(row) {
13886
+ return {
13887
+ name: row.name,
13888
+ type: row.type || "TEXT",
13889
+ nullable: row.notnull === 0,
13890
+ primaryKey: row.pk > 0,
13891
+ defaultValue: row.dflt_value
13892
+ };
13893
+ }
13894
+ function sqliteTableInfoFromRow(row) {
13895
+ return {
13896
+ name: row.name,
13897
+ type: row.type,
13898
+ rowCount: null
13899
+ };
13900
+ }
13901
+ function sqliteRowCountUnionSql(tables) {
13902
+ return tables.map((table) => `SELECT ${escapeSqlString(table)} AS tbl, COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}`).join(" UNION ALL ");
13903
+ }
13904
+ function sqliteRowCountSql(table) {
13905
+ return `SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}`;
13906
+ }
13907
+ function assertReadonlySqliteStatement(sql) {
13908
+ const upper = sql.trim().toUpperCase();
13909
+ if (!SQLITE_READONLY_FIRST_WORDS.has(upper.split(/\s/)[0])) {
13910
+ throw new Error("Only SELECT, PRAGMA, EXPLAIN, and WITH queries are allowed");
13911
+ }
13912
+ if (SQLITE_BLOCKED_KEYWORDS_RE.test(upper)) {
13913
+ throw new Error("Query contains a disallowed statement keyword");
13914
+ }
13915
+ }
13916
+ function stripTrailingSemicolon(sql) {
13917
+ return sql.trim().replace(/;\s*$/, "");
13918
+ }
13919
+ var SQLITE_INTROSPECTION_SQL, SQLITE_READONLY_FIRST_WORDS, SQLITE_BLOCKED_KEYWORDS_RE;
13920
+ var init_sqlite_introspection = __esm(() => {
13921
+ init_sql_utils();
13922
+ SQLITE_INTROSPECTION_SQL = {
13923
+ listTables: "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name",
13924
+ listIndexes: "SELECT name, tbl_name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%' ORDER BY name",
13925
+ listForeignKeyTables: "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND sql NOT LIKE '%VIRTUAL%' ORDER BY name",
13926
+ createStatement: "SELECT sql FROM sqlite_master WHERE name = ?",
13927
+ triggers: "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?"
13928
+ };
13929
+ SQLITE_READONLY_FIRST_WORDS = new Set([
13930
+ "SELECT",
13931
+ "PRAGMA",
13932
+ "EXPLAIN",
13933
+ "WITH"
13934
+ ]);
13935
+ SQLITE_BLOCKED_KEYWORDS_RE = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|DETACH|REPLACE|VACUUM|REINDEX|LOAD_EXTENSION)\b/;
13936
+ });
13937
+
13863
13938
  // web-src/server/database/adapters/sqlite.ts
13864
13939
  function safePrepare(db, sql) {
13865
13940
  const stmt = db.prepare(sql);
@@ -13886,14 +13961,8 @@ function queryRowsToResult(rows, columns) {
13886
13961
  };
13887
13962
  }
13888
13963
  function queryColumns(db, table) {
13889
- const rows = db.prepare(`PRAGMA table_info(${sanitizeIdentifier(table)})`).all();
13890
- return rows.map((row) => ({
13891
- name: row.name,
13892
- type: row.type || "TEXT",
13893
- nullable: row.notnull === 0,
13894
- primaryKey: row.pk > 0,
13895
- defaultValue: row.dflt_value
13896
- }));
13964
+ const rows = db.prepare(sqliteTableInfoSql(table)).all();
13965
+ return rows.map(sqliteColumnFromPragmaRow);
13897
13966
  }
13898
13967
  function wrapDbWithSqlCapture(rawDb) {
13899
13968
  return new Proxy(rawDb, {
@@ -13924,12 +13993,8 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
13924
13993
  model: "sql",
13925
13994
  capabilities: { snapshot: true },
13926
13995
  getTables() {
13927
- const rows = db.prepare("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name").all();
13928
- return rows.map((row) => ({
13929
- name: row.name,
13930
- type: row.type,
13931
- rowCount: null
13932
- }));
13996
+ const rows = db.prepare(SQLITE_INTROSPECTION_SQL.listTables).all();
13997
+ return rows.map(sqliteTableInfoFromRow);
13933
13998
  },
13934
13999
  async getTablesAsync() {
13935
14000
  return this.getTables();
@@ -13941,10 +14006,10 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
13941
14006
  return this.getColumns(table);
13942
14007
  },
13943
14008
  getIndexes() {
13944
- const rows = db.prepare("SELECT name, tbl_name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%' ORDER BY name").all();
14009
+ const rows = db.prepare(SQLITE_INTROSPECTION_SQL.listIndexes).all();
13945
14010
  return rows.map((row) => {
13946
- const info = db.prepare(`PRAGMA index_info(${sanitizeIdentifier(row.name)})`).all();
13947
- const indexList = db.prepare(`PRAGMA index_list(${sanitizeIdentifier(row.tbl_name)})`).all();
14011
+ const info = db.prepare(sqliteIndexInfoSql(row.name)).all();
14012
+ const indexList = db.prepare(sqliteIndexListSql(row.tbl_name)).all();
13948
14013
  const entry = indexList.find((i) => i.name === row.name);
13949
14014
  return {
13950
14015
  name: row.name,
@@ -13958,11 +14023,11 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
13958
14023
  return this.getIndexes();
13959
14024
  },
13960
14025
  getForeignKeys() {
13961
- const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND sql NOT LIKE '%VIRTUAL%' ORDER BY name").all();
14026
+ const tables = db.prepare(SQLITE_INTROSPECTION_SQL.listForeignKeyTables).all();
13962
14027
  const fks = [];
13963
14028
  for (const t of tables) {
13964
14029
  try {
13965
- const rows = db.prepare(`PRAGMA foreign_key_list(${sanitizeIdentifier(t.name)})`).all();
14030
+ const rows = db.prepare(sqliteForeignKeyListSql(t.name)).all();
13966
14031
  for (const row of rows) {
13967
14032
  fks.push({
13968
14033
  fromTable: t.name,
@@ -13989,7 +14054,7 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
13989
14054
  return this.getColumnsMulti(tables);
13990
14055
  },
13991
14056
  getTableRowCount(table) {
13992
- const row = db.prepare(`SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}`).get();
14057
+ const row = db.prepare(sqliteRowCountSql(table)).get();
13993
14058
  return row?.cnt ?? 0;
13994
14059
  },
13995
14060
  async getTableRowCountAsync(table) {
@@ -13999,16 +14064,14 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
13999
14064
  const result = new Map;
14000
14065
  if (tables.length === 0)
14001
14066
  return result;
14002
- const parts = tables.map((t) => `SELECT '${t.replace(/'/g, "''")}' AS tbl, COUNT(*) AS cnt FROM ${sanitizeIdentifier(t)}`);
14003
- const sql = parts.join(" UNION ALL ");
14004
14067
  try {
14005
- const rows = db.prepare(sql).all();
14068
+ const rows = db.prepare(sqliteRowCountUnionSql(tables)).all();
14006
14069
  for (const row of rows) {
14007
14070
  result.set(row.tbl, row.cnt);
14008
14071
  }
14009
14072
  } catch {
14010
14073
  for (const t of tables) {
14011
- const row = db.prepare(`SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(t)}`).get();
14074
+ const row = db.prepare(sqliteRowCountSql(t)).get();
14012
14075
  result.set(t, row?.cnt ?? 0);
14013
14076
  }
14014
14077
  }
@@ -14060,17 +14123,8 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
14060
14123
  };
14061
14124
  },
14062
14125
  executeReadonlyQuery(sql, params, maxRows = 1000) {
14063
- const trimmed = sql.trim();
14064
- const upper = trimmed.toUpperCase();
14065
- const firstWord = upper.split(/\s/)[0];
14066
- if (firstWord !== "SELECT" && firstWord !== "PRAGMA" && firstWord !== "EXPLAIN" && firstWord !== "WITH") {
14067
- throw new Error("Only SELECT, PRAGMA, EXPLAIN, and WITH queries are allowed");
14068
- }
14069
- const BLOCKED_RE = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|DETACH|REPLACE|VACUUM|REINDEX|LOAD_EXTENSION)\b/;
14070
- if (BLOCKED_RE.test(upper)) {
14071
- throw new Error("Query contains a disallowed statement keyword");
14072
- }
14073
- const limited = trimmed.replace(/;\s*$/, "");
14126
+ assertReadonlySqliteStatement(sql);
14127
+ const limited = stripTrailingSemicolon(sql);
14074
14128
  const wrappedSql = `SELECT * FROM (${limited}) LIMIT ${maxRows + 1}`;
14075
14129
  let rows;
14076
14130
  try {
@@ -14101,14 +14155,14 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
14101
14155
  return this.executeReadonlyQuery(sql, params, maxRows);
14102
14156
  },
14103
14157
  getCreateStatement(table) {
14104
- const row = db.prepare("SELECT sql FROM sqlite_master WHERE name = ?").get(table);
14158
+ const row = db.prepare(SQLITE_INTROSPECTION_SQL.createStatement).get(table);
14105
14159
  return row?.sql ?? "";
14106
14160
  },
14107
14161
  async getCreateStatementAsync(table) {
14108
14162
  return this.getCreateStatement(table);
14109
14163
  },
14110
14164
  getTriggers(table) {
14111
- const rows = db.prepare("SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?").all(table);
14165
+ const rows = db.prepare(SQLITE_INTROSPECTION_SQL.triggers).all(table);
14112
14166
  return rows.map((row) => ({ name: row.name, sql: row.sql ?? "" }));
14113
14167
  },
14114
14168
  async getTriggersAsync(table) {
@@ -14188,6 +14242,7 @@ var init_sqlite = __esm(() => {
14188
14242
  init_sql_utils();
14189
14243
  init_sqlite_driver();
14190
14244
  init_sql_capture();
14245
+ init_sqlite_introspection();
14191
14246
  sqliteAdapterFactory = {
14192
14247
  async open(path) {
14193
14248
  const DbClass = await loadSqliteClass();
@@ -14220,13 +14275,13 @@ import {
14220
14275
  openSync,
14221
14276
  readSync,
14222
14277
  realpathSync as realpathSync5,
14223
- statSync as statSync4
14278
+ statSync as statSync5
14224
14279
  } from "node:fs";
14225
14280
  import { lstat, open as open2, readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
14226
14281
  import { basename, join as join9, relative as relative4 } from "node:path";
14227
14282
  function isSqliteFile(fullPath) {
14228
14283
  try {
14229
- const stat3 = statSync4(fullPath);
14284
+ const stat3 = statSync5(fullPath);
14230
14285
  if (!stat3.isFile() || stat3.size < 16)
14231
14286
  return false;
14232
14287
  const buf = Buffer.alloc(16);
@@ -14424,6 +14479,8 @@ function dbKindDisplayName(kind) {
14424
14479
  return "MySQL";
14425
14480
  case "sqlite":
14426
14481
  return "SQLite";
14482
+ case "d1":
14483
+ return "Cloudflare D1";
14427
14484
  }
14428
14485
  }
14429
14486
  function detectDbKindFromContainerPort(port) {
@@ -15850,6 +15907,310 @@ function asAsyncDoc(source) {
15850
15907
  };
15851
15908
  }
15852
15909
 
15910
+ // web-src/server/database/adapters/d1.ts
15911
+ function isD1InternalName(name) {
15912
+ return D1_INTERNAL_NAME_RE.test(name);
15913
+ }
15914
+ function isD1HttpError(err) {
15915
+ return err instanceof D1HttpError;
15916
+ }
15917
+ function d1ErrorMessage(envelope, status, sql) {
15918
+ const first = envelope.errors?.find((entry) => entry?.message);
15919
+ const detail = first?.message?.replace(/\s+/g, " ").trim().slice(0, 240);
15920
+ const statement = sql.replace(/\s+/g, " ").trim().slice(0, 160);
15921
+ return `${detail || `D1 HTTP ${status}`} (sql: ${statement})`;
15922
+ }
15923
+ function toDbValue(value) {
15924
+ if (value === null || value === undefined)
15925
+ return null;
15926
+ if (typeof value === "number" || typeof value === "string")
15927
+ return value;
15928
+ if (typeof value === "boolean")
15929
+ return value;
15930
+ if (Array.isArray(value)) {
15931
+ return value.every((byte) => typeof byte === "number") ? new Uint8Array(value) : JSON.stringify(value);
15932
+ }
15933
+ if (typeof value === "object")
15934
+ return JSON.stringify(value);
15935
+ return String(value);
15936
+ }
15937
+ function parseRawResult(result) {
15938
+ const columns = Array.isArray(result?.results?.columns) ? result.results.columns.map((name) => String(name)) : [];
15939
+ const rawRows = Array.isArray(result?.results?.rows) ? result.results.rows : [];
15940
+ const rows = rawRows.map((row) => Array.isArray(row) ? row.map(toDbValue) : []);
15941
+ return {
15942
+ columns,
15943
+ columnTypes: columns.map(() => "TEXT"),
15944
+ rows,
15945
+ rowCount: rows.length
15946
+ };
15947
+ }
15948
+ async function d1Fetch(url, init, signal) {
15949
+ if (signal?.aborted)
15950
+ throw new D1HttpError(503, "D1 request aborted");
15951
+ const controller = new AbortController;
15952
+ const onParentAbort = () => controller.abort();
15953
+ signal?.addEventListener("abort", onParentAbort, { once: true });
15954
+ const timer = setTimeout(() => controller.abort(), d1RequestTimeoutMs);
15955
+ try {
15956
+ const d1FetchImpl = d1FetchOverride ?? globalThis.fetch;
15957
+ return await d1FetchImpl(url, { ...init, signal: controller.signal });
15958
+ } catch (err) {
15959
+ if (signal?.aborted)
15960
+ throw new D1HttpError(503, "D1 request aborted");
15961
+ if (controller.signal.aborted) {
15962
+ throw new D1HttpError(503, `D1 request timed out after ${d1RequestTimeoutMs}ms`);
15963
+ }
15964
+ throw new D1HttpError(503, `D1 request failed: ${err instanceof Error ? err.message : String(err)}`);
15965
+ } finally {
15966
+ clearTimeout(timer);
15967
+ signal?.removeEventListener("abort", onParentAbort);
15968
+ }
15969
+ }
15970
+ function createD1Adapter(config) {
15971
+ const baseUrl = (config.apiBaseUrl || DEFAULT_D1_API_BASE_URL).replace(/\/$/, "");
15972
+ const rawUrl = `${baseUrl}/accounts/${encodeURIComponent(config.accountId)}/d1/database/${encodeURIComponent(config.databaseId)}/raw`;
15973
+ const tableMetaCache = createTableMetaCache();
15974
+ async function runSql(sql, params = [], signal) {
15975
+ if (!config.apiToken) {
15976
+ throw new D1HttpError(401, "D1 API token is missing. Reopen the connection dialog and enter the API token again (credentials are never stored in the repository).");
15977
+ }
15978
+ recordSql(sql);
15979
+ const res = await d1Fetch(rawUrl, {
15980
+ method: "POST",
15981
+ headers: {
15982
+ "Content-Type": "application/json",
15983
+ Authorization: `Bearer ${config.apiToken}`
15984
+ },
15985
+ body: JSON.stringify({ sql, params })
15986
+ }, signal);
15987
+ let envelope;
15988
+ try {
15989
+ envelope = await res.json();
15990
+ } catch {
15991
+ throw new D1HttpError(res.status, `D1 HTTP ${res.status}: invalid JSON`);
15992
+ }
15993
+ if (!res.ok || envelope.success === false) {
15994
+ const status = res.ok ? 400 : res.status;
15995
+ throw new D1HttpError(status, d1ErrorMessage(envelope, status, sql));
15996
+ }
15997
+ return parseRawResult(envelope.result?.[0]);
15998
+ }
15999
+ async function runSqlRecords(sql, params = [], signal) {
16000
+ const result = await runSql(sql, params, signal);
16001
+ return result.rows.map((row) => {
16002
+ const record = {};
16003
+ result.columns.forEach((column, index) => {
16004
+ record[column] = row[index] ?? null;
16005
+ });
16006
+ return record;
16007
+ });
16008
+ }
16009
+ async function loadColumns(table, signal) {
16010
+ const rows = await runSqlRecords(sqliteTableInfoSql(table), [], signal);
16011
+ return rows.map(sqliteColumnFromPragmaRow);
16012
+ }
16013
+ function getColumnsAsync(table, signal) {
16014
+ return tableMetaCache.getColumns(table, () => loadColumns(table, signal));
16015
+ }
16016
+ async function countRows(table, signal) {
16017
+ const result = await runSql(sqliteRowCountSql(table), [], signal);
16018
+ return Number(result.rows[0]?.[0] ?? 0);
16019
+ }
16020
+ async function selectPage(table, columns, options, signal) {
16021
+ const order = buildOrderClause(filterOrderByColumns(options.orderBy, columns.map((column) => column.name)));
16022
+ const whereClause = options.where ? ` WHERE ${options.where}` : "";
16023
+ return runSql(`SELECT * FROM ${sanitizeIdentifier(table)}${whereClause}${order} LIMIT ? OFFSET ?`, [...options.params ?? [], options.limit, options.offset], signal);
16024
+ }
16025
+ const adapter = {
16026
+ kind: "d1",
16027
+ model: "sql",
16028
+ capabilities: { snapshot: true },
16029
+ async getTablesAsync(signal) {
16030
+ const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listTables, [], signal);
16031
+ return rows.filter((row) => !isD1InternalName(row.name)).map(sqliteTableInfoFromRow);
16032
+ },
16033
+ getColumnsAsync,
16034
+ async getColumnsMultiAsync(tables, signal) {
16035
+ const entries = await Promise.all(tables.map(async (table) => [table, await getColumnsAsync(table, signal)]));
16036
+ return new Map(entries);
16037
+ },
16038
+ async getIndexesAsync(signal) {
16039
+ const indexes = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listIndexes, [], signal);
16040
+ const described = await Promise.all(indexes.filter((index) => !isD1InternalName(index.name) && !isD1InternalName(index.tbl_name)).map(async (index) => {
16041
+ try {
16042
+ const [info, indexList] = await Promise.all([
16043
+ runSqlRecords(sqliteIndexInfoSql(index.name), [], signal),
16044
+ runSqlRecords(sqliteIndexListSql(index.tbl_name), [], signal)
16045
+ ]);
16046
+ const entry = indexList.find((row) => row.name === index.name);
16047
+ return {
16048
+ name: index.name,
16049
+ table: index.tbl_name,
16050
+ columns: info.map((row) => row.name),
16051
+ unique: entry ? Number(entry.unique) === 1 : false
16052
+ };
16053
+ } catch (err) {
16054
+ if (isAbortLikeError(err, signal))
16055
+ throw err;
16056
+ return null;
16057
+ }
16058
+ }));
16059
+ return described.filter((index) => index !== null);
16060
+ },
16061
+ async getForeignKeysAsync(signal) {
16062
+ const tables = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listForeignKeyTables, [], signal);
16063
+ const perTable = await Promise.all(tables.filter((table) => !isD1InternalName(table.name)).map(async (table) => {
16064
+ try {
16065
+ const rows = await runSqlRecords(sqliteForeignKeyListSql(table.name), [], signal);
16066
+ return rows.map((row) => ({
16067
+ fromTable: table.name,
16068
+ fromColumn: row.from,
16069
+ toTable: row.table,
16070
+ toColumn: row.to
16071
+ }));
16072
+ } catch (err) {
16073
+ if (isAbortLikeError(err, signal))
16074
+ throw err;
16075
+ return [];
16076
+ }
16077
+ }));
16078
+ return perTable.flat();
16079
+ },
16080
+ getTableRowCountAsync(table, signal) {
16081
+ return tableMetaCache.getRowCount(table, () => countRows(table, signal));
16082
+ },
16083
+ async getTableRowCountsAsync(tables, signal) {
16084
+ const result = new Map;
16085
+ const countable = tables.filter((table) => !isD1InternalName(table));
16086
+ if (countable.length === 0)
16087
+ return result;
16088
+ try {
16089
+ const rows = await runSqlRecords(sqliteRowCountUnionSql(countable), [], signal);
16090
+ for (const row of rows)
16091
+ result.set(String(row.tbl), Number(row.cnt));
16092
+ return result;
16093
+ } catch {
16094
+ for (const table of countable) {
16095
+ try {
16096
+ result.set(table, await countRows(table, signal));
16097
+ } catch (err) {
16098
+ if (isAbortLikeError(err, signal))
16099
+ throw err;
16100
+ }
16101
+ }
16102
+ return result;
16103
+ }
16104
+ },
16105
+ async getTablePageAsync(table, options, signal) {
16106
+ const columns = await getColumnsAsync(table, signal);
16107
+ return selectPage(table, columns, options, signal);
16108
+ },
16109
+ async getTablePageWithMeta(table, options, signal) {
16110
+ const columns = await getColumnsAsync(table, signal);
16111
+ const [page, totalRows] = await Promise.all([
16112
+ selectPage(table, columns, options, signal),
16113
+ countRows(table, signal)
16114
+ ]);
16115
+ return {
16116
+ columns,
16117
+ rows: page.rows,
16118
+ rowCount: page.rowCount,
16119
+ totalRows
16120
+ };
16121
+ },
16122
+ async getFilteredTablePageWithMeta(table, options, signal) {
16123
+ const columns = await getColumnsAsync(table, signal);
16124
+ const columnNames = columns.map((column) => column.name);
16125
+ const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite", filterExactColumns(options.exact, columnNames));
16126
+ const whereClause = filter.where ? ` WHERE ${filter.where}` : "";
16127
+ const [page, countResult] = await Promise.all([
16128
+ selectPage(table, columns, { ...options, where: filter.where, params: filter.params }, signal),
16129
+ runSql(`SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}${whereClause}`, filter.params, signal)
16130
+ ]);
16131
+ return {
16132
+ columns,
16133
+ rows: page.rows,
16134
+ rowCount: page.rowCount,
16135
+ totalRows: Number(countResult.rows[0]?.[0] ?? 0)
16136
+ };
16137
+ },
16138
+ async executeReadonlyQueryAsync(sql, params, maxRows = 1000, signal) {
16139
+ assertReadonlySqliteStatement(sql);
16140
+ const limited = stripTrailingSemicolon(sql);
16141
+ let result;
16142
+ try {
16143
+ result = await runSql(`SELECT * FROM (${limited}) LIMIT ${maxRows + 1}`, params, signal);
16144
+ } catch (wrapErr) {
16145
+ try {
16146
+ result = await runSql(`${limited} LIMIT ${maxRows + 1}`, params, signal);
16147
+ } catch {
16148
+ throw wrapErr;
16149
+ }
16150
+ }
16151
+ return result.rows.length > maxRows ? { ...result, rows: result.rows.slice(0, maxRows), rowCount: maxRows } : result;
16152
+ },
16153
+ invalidateTableMetaCache(table) {
16154
+ tableMetaCache.invalidate(table);
16155
+ },
16156
+ async getCreateStatementAsync(table, signal) {
16157
+ const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.createStatement, [table], signal);
16158
+ return rows[0]?.sql ?? "";
16159
+ },
16160
+ async getTriggersAsync(table, signal) {
16161
+ const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.triggers, [table], signal);
16162
+ return rows.map((row) => ({ name: row.name, sql: row.sql ?? "" }));
16163
+ },
16164
+ close() {},
16165
+ async* iterateForSnapshot(table, signal) {
16166
+ const columns = await adapter.getColumnsAsync(table, signal);
16167
+ const colNames = columns.map((column) => column.name);
16168
+ const pkColumns = columns.filter((column) => column.primaryKey).map((column) => column.name);
16169
+ let offset = 0;
16170
+ let rowIndex = 0;
16171
+ for (;; ) {
16172
+ if (signal?.aborted)
16173
+ return;
16174
+ const result = await adapter.getTablePageAsync(table, { offset, limit: SQL_SNAPSHOT_BATCH_SIZE }, signal);
16175
+ if (result.rows.length === 0)
16176
+ return;
16177
+ for (const row of result.rows) {
16178
+ yield {
16179
+ keyJson: buildRowKeyJson(pkColumns, colNames, row, rowIndex),
16180
+ rowHash: computeRowHash(colNames, row),
16181
+ payloadJson: rowToPayloadJson(colNames, row)
16182
+ };
16183
+ rowIndex++;
16184
+ }
16185
+ offset += result.rows.length;
16186
+ if (result.rows.length < SQL_SNAPSHOT_BATCH_SIZE)
16187
+ return;
16188
+ }
16189
+ },
16190
+ async listSnapshotContainers() {
16191
+ const tables = await adapter.getTablesAsync();
16192
+ return tables.filter((table) => table.type === "table").map((table) => ({ id: table.name, label: table.name }));
16193
+ }
16194
+ };
16195
+ return adapter;
16196
+ }
16197
+ var DEFAULT_D1_API_BASE_URL = "https://api.cloudflare.com/client/v4", D1_INTERNAL_NAME_RE, DEFAULT_D1_REQUEST_TIMEOUT_MS = 20000, d1RequestTimeoutMs, d1FetchOverride = null, D1HttpError;
16198
+ var init_d1 = __esm(() => {
16199
+ init_sql_snapshot();
16200
+ init_sql_utils();
16201
+ init_sql_capture();
16202
+ init_sqlite_introspection();
16203
+ D1_INTERNAL_NAME_RE = /^_cf_/i;
16204
+ d1RequestTimeoutMs = DEFAULT_D1_REQUEST_TIMEOUT_MS;
16205
+ D1HttpError = class D1HttpError extends Error {
16206
+ status;
16207
+ constructor(status, message) {
16208
+ super(message);
16209
+ this.status = status;
16210
+ }
16211
+ };
16212
+ });
16213
+
15853
16214
  // web-src/server/database/adapters/dynamodb.ts
15854
16215
  import { spawnSync as spawnSync5 } from "node:child_process";
15855
16216
  import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
@@ -16388,6 +16749,136 @@ var init_connection_pool = __esm(() => {
16388
16749
  pool = new Map;
16389
16750
  });
16390
16751
 
16752
+ // web-src/server/database/credential-store.ts
16753
+ function isKeychainAvailable() {
16754
+ if (keychainEnabledOverride !== null)
16755
+ return keychainEnabledOverride;
16756
+ if (false)
16757
+ ;
16758
+ return process.platform === "darwin";
16759
+ }
16760
+ function accountFor(cwd, connectionId) {
16761
+ return `${cwd}#${connectionId}`;
16762
+ }
16763
+ function quoteSecurityArg(value) {
16764
+ if (hasControlCharacter(value))
16765
+ return null;
16766
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
16767
+ }
16768
+ async function runSecurityAsync(opts) {
16769
+ if (!keychainSpawnOverride && false) {}
16770
+ const spawn3 = keychainSpawnOverride ?? spawnCollectAsync;
16771
+ const result = await spawn3({
16772
+ command: SECURITY_COMMAND,
16773
+ args: opts.args,
16774
+ ...opts.input === undefined ? {} : { input: opts.input },
16775
+ timeoutMs: KEYCHAIN_TIMEOUT_MS,
16776
+ abortMessage: "keychain access aborted",
16777
+ timeoutMessage: `keychain access timed out after ${KEYCHAIN_TIMEOUT_MS}ms`,
16778
+ rejectOnError: false
16779
+ });
16780
+ return {
16781
+ stdout: result.stdout.toString("utf8"),
16782
+ stderr: result.stderr.toString("utf8"),
16783
+ code: result.code
16784
+ };
16785
+ }
16786
+ function warnKeychain(action, detail) {
16787
+ console.warn(`[code-viewer] keychain ${action} failed: ${detail.replace(/\s+/g, " ").trim().slice(0, 200)}`);
16788
+ }
16789
+ async function saveConnectionSecretsAsync(cwd, connectionId, secrets) {
16790
+ if (!isKeychainAvailable())
16791
+ return false;
16792
+ if (Object.keys(secrets).length === 0) {
16793
+ return deleteConnectionSecretsAsync(cwd, connectionId);
16794
+ }
16795
+ const account = quoteSecurityArg(accountFor(cwd, connectionId));
16796
+ const label = quoteSecurityArg(`code-viewer: ${connectionId}`);
16797
+ if (!account || !label) {
16798
+ warnKeychain("save", "connection id or path contains control characters");
16799
+ return false;
16800
+ }
16801
+ const payload = Buffer.from(JSON.stringify(secrets), "utf8").toString("base64");
16802
+ try {
16803
+ const result = await runSecurityAsync({
16804
+ args: ["-i"],
16805
+ input: `add-generic-password -U -s "${KEYCHAIN_SERVICE}" -a ${account} -l ${label} -w "${payload}"
16806
+ `
16807
+ });
16808
+ if (result.code !== 0) {
16809
+ warnKeychain("save", result.stderr || `exit ${result.code}`);
16810
+ return false;
16811
+ }
16812
+ return true;
16813
+ } catch (err) {
16814
+ warnKeychain("save", err instanceof Error ? err.message : String(err));
16815
+ return false;
16816
+ }
16817
+ }
16818
+ async function loadConnectionSecretsAsync(cwd, connectionId) {
16819
+ if (!isKeychainAvailable())
16820
+ return null;
16821
+ const account = accountFor(cwd, connectionId);
16822
+ if (hasControlCharacter(account))
16823
+ return null;
16824
+ try {
16825
+ const result = await runSecurityAsync({
16826
+ args: [
16827
+ "find-generic-password",
16828
+ "-s",
16829
+ KEYCHAIN_SERVICE,
16830
+ "-a",
16831
+ account,
16832
+ "-w"
16833
+ ]
16834
+ });
16835
+ if (result.code === ERR_SEC_ITEM_NOT_FOUND)
16836
+ return null;
16837
+ if (result.code !== 0) {
16838
+ warnKeychain("read", result.stderr || `exit ${result.code}`);
16839
+ return null;
16840
+ }
16841
+ const decoded = Buffer.from(result.stdout.trim(), "base64").toString("utf8");
16842
+ const parsed = JSON.parse(decoded);
16843
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
16844
+ return null;
16845
+ }
16846
+ const secrets = {};
16847
+ for (const [key, value] of Object.entries(parsed)) {
16848
+ if (typeof value === "string")
16849
+ secrets[key] = value;
16850
+ }
16851
+ return Object.keys(secrets).length > 0 ? secrets : null;
16852
+ } catch (err) {
16853
+ warnKeychain("read", err instanceof Error ? err.message : String(err));
16854
+ return null;
16855
+ }
16856
+ }
16857
+ async function deleteConnectionSecretsAsync(cwd, connectionId) {
16858
+ if (!isKeychainAvailable())
16859
+ return false;
16860
+ const account = accountFor(cwd, connectionId);
16861
+ if (hasControlCharacter(account))
16862
+ return false;
16863
+ try {
16864
+ const result = await runSecurityAsync({
16865
+ args: ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account]
16866
+ });
16867
+ if (result.code !== 0 && result.code !== ERR_SEC_ITEM_NOT_FOUND) {
16868
+ warnKeychain("delete", result.stderr || `exit ${result.code}`);
16869
+ return false;
16870
+ }
16871
+ return true;
16872
+ } catch (err) {
16873
+ warnKeychain("delete", err instanceof Error ? err.message : String(err));
16874
+ return false;
16875
+ }
16876
+ }
16877
+ var SECURITY_COMMAND = "/usr/bin/security", KEYCHAIN_SERVICE = "code-viewer", KEYCHAIN_TIMEOUT_MS = 5000, ERR_SEC_ITEM_NOT_FOUND = 44, keychainEnabledOverride = null, keychainSpawnOverride = null;
16878
+ var init_credential_store = __esm(() => {
16879
+ init_spawn_runner();
16880
+ });
16881
+
16391
16882
  // web-src/server/database/connections-store.ts
16392
16883
  import { randomUUID } from "node:crypto";
16393
16884
  import { chmod } from "node:fs/promises";
@@ -16395,15 +16886,45 @@ import { join as join12 } from "node:path";
16395
16886
  function secretKey(cwd, id) {
16396
16887
  return `${cwd}\x00${id}`;
16397
16888
  }
16889
+ async function hydrateFromKeychainAsync(cwd, connections) {
16890
+ if (!isKeychainAvailable())
16891
+ return;
16892
+ await Promise.all(connections.map((connection) => {
16893
+ const key = secretKey(cwd, connection.id);
16894
+ if (runtimeSecrets.has(key))
16895
+ return;
16896
+ const inFlight = keychainLookups.get(key);
16897
+ if (inFlight)
16898
+ return inFlight;
16899
+ const lookup = loadConnectionSecretsAsync(cwd, connection.id).then((stored) => {
16900
+ if (stored)
16901
+ runtimeSecrets.set(key, extractSecrets(stored));
16902
+ }).catch(() => {
16903
+ keychainLookups.delete(key);
16904
+ });
16905
+ keychainLookups.set(key, lookup);
16906
+ return lookup;
16907
+ }));
16908
+ }
16398
16909
  function extractSecrets(value) {
16399
- return {
16400
- ...typeof value.user === "string" ? { user: value.user } : {},
16401
- ...typeof value.username === "string" ? { username: value.username } : {},
16402
- ...typeof value.accessKeyId === "string" ? { accessKeyId: value.accessKeyId } : {},
16403
- ...typeof value.password === "string" ? { password: value.password } : {},
16404
- ...typeof value.secretAccessKey === "string" ? { secretAccessKey: value.secretAccessKey } : {},
16405
- ...typeof value.sessionToken === "string" ? { sessionToken: value.sessionToken } : {}
16406
- };
16910
+ const secrets = {};
16911
+ for (const field of RUNTIME_ONLY_FIELDS) {
16912
+ if (typeof value[field] === "string")
16913
+ secrets[field] = value[field];
16914
+ }
16915
+ return secrets;
16916
+ }
16917
+ function preservedSecrets(input, existing) {
16918
+ if (!existing)
16919
+ return {};
16920
+ const preserved = {};
16921
+ const source = existing;
16922
+ for (const field of PRESERVED_SECRET_FIELDS) {
16923
+ if (input[field] === undefined && field in source) {
16924
+ preserved[field] = source[field];
16925
+ }
16926
+ }
16927
+ return preserved;
16407
16928
  }
16408
16929
  function withRuntimeSecrets(cwd, connection) {
16409
16930
  return {
@@ -16497,6 +17018,19 @@ function sanitizeConnection(raw) {
16497
17018
  password: requiredString(input.password)
16498
17019
  };
16499
17020
  }
17021
+ if (input.kind === "d1") {
17022
+ const accountId = requiredString(input.accountId, 128).trim();
17023
+ const databaseId = requiredString(input.databaseId, 128).trim();
17024
+ if (!accountId || !databaseId)
17025
+ return null;
17026
+ return {
17027
+ ...base,
17028
+ kind: "d1",
17029
+ accountId,
17030
+ databaseId,
17031
+ apiToken: requiredString(input.apiToken)
17032
+ };
17033
+ }
16500
17034
  if (input.kind === "s3" || input.kind === "dynamodb") {
16501
17035
  const endpoint = validEndpoint(input.endpoint);
16502
17036
  const region = requiredString(input.region).trim();
@@ -16515,16 +17049,19 @@ function sanitizeConnection(raw) {
16515
17049
  }
16516
17050
  return null;
16517
17051
  }
17052
+ function assertConnectionCredentials(connection) {
17053
+ const missing = !connection || (connection.kind === "postgresql" || connection.kind === "mysql") && !connection.user || (connection.kind === "s3" || connection.kind === "dynamodb") && !connection.accessKeyId || connection.kind === "d1" && !connection.apiToken;
17054
+ if (!connection || missing) {
17055
+ throw new Error("invalid datastore connection");
17056
+ }
17057
+ return connection;
17058
+ }
16518
17059
  function validateDatastoreConnection(raw, fallbackId = "connection:0000000000000000") {
16519
17060
  const input = raw && typeof raw === "object" ? raw : {};
16520
- const connection = sanitizeConnection({
17061
+ return assertConnectionCredentials(sanitizeConnection({
16521
17062
  ...input,
16522
17063
  id: typeof input.id === "string" && input.id ? input.id : fallbackId
16523
- });
16524
- if (!connection || (connection.kind === "postgresql" || connection.kind === "mysql") && !connection.user || (connection.kind === "s3" || connection.kind === "dynamodb") && !connection.accessKeyId) {
16525
- throw new Error("invalid datastore connection");
16526
- }
16527
- return connection;
17064
+ }));
16528
17065
  }
16529
17066
  function sanitizeState(raw) {
16530
17067
  if (!raw || typeof raw !== "object")
@@ -16552,7 +17089,9 @@ async function protectFile(cwd) {
16552
17089
  });
16553
17090
  }
16554
17091
  async function loadDatastoreConnections(cwd) {
16555
- return (await store.load(cwd)).connections.map((connection) => withRuntimeSecrets(cwd, connection));
17092
+ const { connections } = await store.load(cwd);
17093
+ await hydrateFromKeychainAsync(cwd, connections);
17094
+ return connections.map((connection) => withRuntimeSecrets(cwd, connection));
16556
17095
  }
16557
17096
  async function findDatastoreConnection(cwd, id) {
16558
17097
  return (await loadDatastoreConnections(cwd)).find((entry) => entry.id === id) ?? null;
@@ -16564,17 +17103,12 @@ async function saveDatastoreConnection(cwd, raw) {
16564
17103
  const result = await store.update(cwd, (state) => {
16565
17104
  const storedExisting = state.connections.find((entry) => entry.id === id);
16566
17105
  const existing = storedExisting ? withRuntimeSecrets(cwd, storedExisting) : undefined;
16567
- const merged = sanitizeConnection({
17106
+ const merged = assertConnectionCredentials(sanitizeConnection({
16568
17107
  ...existing ?? {},
16569
17108
  ...input,
16570
17109
  id,
16571
- password: input.password === undefined && existing && "password" in existing ? existing.password : input.password,
16572
- secretAccessKey: input.secretAccessKey === undefined && existing && "secretAccessKey" in existing ? existing.secretAccessKey : input.secretAccessKey,
16573
- sessionToken: input.sessionToken === undefined && existing && "sessionToken" in existing ? existing.sessionToken : input.sessionToken
16574
- });
16575
- if (!merged || (merged.kind === "postgresql" || merged.kind === "mysql") && !merged.user || (merged.kind === "s3" || merged.kind === "dynamodb") && !merged.accessKeyId) {
16576
- throw new Error("invalid datastore connection");
16577
- }
17110
+ ...preservedSecrets(input, existing)
17111
+ }));
16578
17112
  const connections = state.connections.filter((entry) => entry.id !== id);
16579
17113
  if (!storedExisting && connections.length >= MAX_CONNECTIONS2) {
16580
17114
  throw new Error("too many datastore connections");
@@ -16585,7 +17119,10 @@ async function saveDatastoreConnection(cwd, raw) {
16585
17119
  result: merged
16586
17120
  };
16587
17121
  });
16588
- runtimeSecrets.set(secretKey(cwd, result.id), extractSecrets(result));
17122
+ const secrets = extractSecrets(result);
17123
+ runtimeSecrets.set(secretKey(cwd, result.id), secrets);
17124
+ keychainLookups.set(secretKey(cwd, result.id), Promise.resolve());
17125
+ await saveConnectionSecretsAsync(cwd, result.id, secrets);
16589
17126
  await protectFile(cwd);
16590
17127
  return result;
16591
17128
  }
@@ -16598,8 +17135,10 @@ async function deleteDatastoreConnection(cwd, id) {
16598
17135
  };
16599
17136
  });
16600
17137
  runtimeSecrets.delete(secretKey(cwd, id));
17138
+ keychainLookups.delete(secretKey(cwd, id));
17139
+ const secretsRemoved = isKeychainAvailable() ? await deleteConnectionSecretsAsync(cwd, id) : true;
16601
17140
  await protectFile(cwd);
16602
- return deleted;
17141
+ return { deleted, secretsRemoved };
16603
17142
  }
16604
17143
  function connectionToFileInfo(connection) {
16605
17144
  return {
@@ -16612,25 +17151,33 @@ function connectionToFileInfo(connection) {
16612
17151
  };
16613
17152
  }
16614
17153
  function publicConnection(connection) {
16615
- const {
16616
- password: _password,
16617
- user: _user,
16618
- username: _username,
16619
- accessKeyId: _accessKeyId,
16620
- ...withoutPassword
16621
- } = connection;
16622
- const {
16623
- secretAccessKey: _secret,
16624
- sessionToken: _token,
16625
- ...safe
16626
- } = withoutPassword;
17154
+ const safe = { ...connection };
17155
+ for (const field of RUNTIME_ONLY_FIELDS)
17156
+ delete safe[field];
16627
17157
  return safe;
16628
17158
  }
16629
- 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;
17159
+ 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, RUNTIME_ONLY_FIELDS, PRESERVED_SECRET_FIELDS, runtimeSecrets, keychainLookups, store;
16630
17160
  var init_connections_store = __esm(() => {
16631
17161
  init_json_store();
17162
+ init_credential_store();
16632
17163
  MAX_JSON_BYTES = 256 * 1024;
17164
+ RUNTIME_ONLY_FIELDS = [
17165
+ "user",
17166
+ "username",
17167
+ "accessKeyId",
17168
+ "password",
17169
+ "secretAccessKey",
17170
+ "sessionToken",
17171
+ "apiToken"
17172
+ ];
17173
+ PRESERVED_SECRET_FIELDS = [
17174
+ "password",
17175
+ "secretAccessKey",
17176
+ "sessionToken",
17177
+ "apiToken"
17178
+ ];
16633
17179
  runtimeSecrets = new Map;
17180
+ keychainLookups = new Map;
16634
17181
  store = createJsonFileStore({
16635
17182
  filePath: connectionsFilePath,
16636
17183
  empty: emptyState,
@@ -16655,7 +17202,7 @@ function escapeLikeTerm(term) {
16655
17202
  return term.replace(/=/g, "==").replace(/%/g, "=%").replace(/_/g, "=_");
16656
17203
  }
16657
17204
  async function searchTableAsync(adapter, table, columns, term, maxHits, includeNonText, pkColumns, signal) {
16658
- const kind = adapter.kind;
17205
+ const kind = adapter.kind === "d1" ? "sqlite" : adapter.kind;
16659
17206
  const searchCols = includeNonText ? columns.filter((c) => c.type.toUpperCase() !== "BLOB" && c.type.toUpperCase() !== "BYTEA") : columns.filter((c) => isTextLikeType(c.type));
16660
17207
  if (searchCols.length === 0)
16661
17208
  return [];
@@ -17024,6 +17571,9 @@ function handleError(prefix, action, err, signal) {
17024
17571
  if (isDockerComposeServiceUnavailableError(err)) {
17025
17572
  return textError(message, err.status);
17026
17573
  }
17574
+ if (isD1HttpError(err)) {
17575
+ return textError(message, err.status);
17576
+ }
17027
17577
  if (isFilesystemAccessError(err)) {
17028
17578
  return textError(`failed to ${action}`, 500);
17029
17579
  }
@@ -17031,6 +17581,7 @@ function handleError(prefix, action, err, signal) {
17031
17581
  }
17032
17582
  var DEFAULT_MAX_DOCKER_ADAPTER_CACHE = 8, DEFAULT_DOCKER_ADAPTER_IDLE_MS, MAX_LOGGED_ERROR_BODY = 500, logQueue;
17033
17583
  var init_handle_shared = __esm(() => {
17584
+ init_d1();
17034
17585
  init_docker_utils();
17035
17586
  init_connections_store();
17036
17587
  init_discovery();
@@ -19202,6 +19753,10 @@ function ensureInit() {
19202
19753
  initialized = true;
19203
19754
  }
19204
19755
  async function getAdapter(r, _cwd, signal) {
19756
+ if (r.d1) {
19757
+ const connection = r.d1;
19758
+ return dockerAdapterCache.getOrOpenAsync(r.dbId, () => createD1Adapter(connection));
19759
+ }
19205
19760
  if (r.saved) {
19206
19761
  const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
19207
19762
  return dockerAdapterCache.getOrOpenAsync(cacheKey, () => createSqlCliAdapter({ ...r.saved, schema: r.schema }));
@@ -19257,6 +19812,9 @@ async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
19257
19812
  const connection = await findDatastoreConnection(cwd, dbParam);
19258
19813
  if (!connection)
19259
19814
  return textError("datastore connection not found", 404);
19815
+ if (connection.kind === "d1") {
19816
+ return { resolved: dbParam, dbId: dbParam, d1: connection };
19817
+ }
19260
19818
  if (connection.kind !== "postgresql" && connection.kind !== "mysql") {
19261
19819
  return textError(`${connection.kind} must use its datastore routes`, 400);
19262
19820
  }
@@ -20466,7 +21024,7 @@ async function handleDbUiGet(cwd) {
20466
21024
  return jsonLoadResponse(() => loadDbUiState(cwd), "db UI", "failed to load db UI state");
20467
21025
  }
20468
21026
  function closeSavedConnection(id, kind) {
20469
- if (kind === "postgresql" || kind === "mysql") {
21027
+ if (kind === "postgresql" || kind === "mysql" || kind === "d1") {
20470
21028
  dockerAdapterCache.close(id);
20471
21029
  dockerAdapterCache.closePrefix(`${id}\x00`);
20472
21030
  return;
@@ -20498,9 +21056,9 @@ async function handleConnections(cwd, req) {
20498
21056
  const existing = await findDatastoreConnection(cwd, id);
20499
21057
  if (!existing)
20500
21058
  return textError("datastore connection not found", 404);
20501
- await deleteDatastoreConnection(cwd, id);
21059
+ const { secretsRemoved } = await deleteDatastoreConnection(cwd, id);
20502
21060
  closeSavedConnection(id, existing.kind);
20503
- return json({ ok: true });
21061
+ return json({ ok: true, secretsRemoved });
20504
21062
  }
20505
21063
  async function probeDatastoreConnection(connection, signal) {
20506
21064
  if (connection.kind === "postgresql" || connection.kind === "mysql") {
@@ -20512,6 +21070,15 @@ async function probeDatastoreConnection(connection, signal) {
20512
21070
  }
20513
21071
  return;
20514
21072
  }
21073
+ if (connection.kind === "d1") {
21074
+ const adapter2 = createD1Adapter(connection);
21075
+ try {
21076
+ await adapter2.getTablesAsync(signal);
21077
+ } finally {
21078
+ adapter2.close();
21079
+ }
21080
+ return;
21081
+ }
20515
21082
  if (connection.kind === "redis") {
20516
21083
  const adapter2 = createRedisAdapter(connection);
20517
21084
  try {
@@ -20831,6 +21398,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
20831
21398
  var initialized = false, dockerAdapterCache, DEFAULT_DB_FILE_DISCOVERY_DEPS, MAX_SCHEMA_NAME_LEN2 = 1024, MAX_COLUMN_VALUE_PAIRS = 64, MAX_FILTER_COLUMN_LEN = 128, MAX_FILTER_VALUE_LEN = 4096, DB_QUERY_DEFAULT_MAX_ROWS = 1000, DB_QUERY_HARD_CAP_MAX_ROWS = 1e4, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_DB_UI_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
20832
21399
  var init_handle = __esm(() => {
20833
21400
  init_state_store();
21401
+ init_d1();
20834
21402
  init_docker();
20835
21403
  init_docker_utils();
20836
21404
  init_dynamodb();
@@ -20900,7 +21468,7 @@ var init_handle = __esm(() => {
20900
21468
  });
20901
21469
 
20902
21470
  // web-src/server/doctor.ts
20903
- import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
21471
+ import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync6 } from "node:fs";
20904
21472
  import { dirname as dirname5, join as join16, relative as relative6 } from "node:path";
20905
21473
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20906
21474
  function statusWorse(a, b) {
@@ -21093,7 +21661,7 @@ async function checkSqlite(cwd) {
21093
21661
  async function trySnapshotDbOpen(cwd) {
21094
21662
  const dbPath = join16(cwd, SNAPSHOT_DB_REL);
21095
21663
  try {
21096
- statSync5(dbPath);
21664
+ statSync6(dbPath);
21097
21665
  } catch {
21098
21666
  return { kind: "skipped" };
21099
21667
  }
@@ -21122,7 +21690,7 @@ function checkSnapshotStore(cwd) {
21122
21690
  dirDetail = `${dir} (writable)`;
21123
21691
  } catch {
21124
21692
  try {
21125
- statSync5(dir);
21693
+ statSync6(dir);
21126
21694
  dirStatus = "error";
21127
21695
  dirDetail = `${dir} (not writable)`;
21128
21696
  dirHint = "Snapshot creation will fail until the directory is writable. " + "Check filesystem permissions on the .code-viewer directory.";
@@ -21132,7 +21700,7 @@ function checkSnapshotStore(cwd) {
21132
21700
  }
21133
21701
  let dbDetail = dbPath;
21134
21702
  try {
21135
- const stat3 = statSync5(dbPath);
21703
+ const stat3 = statSync6(dbPath);
21136
21704
  dbDetail = `${dbPath} (${stat3.size.toLocaleString()} bytes)`;
21137
21705
  } catch {
21138
21706
  dbDetail = `${dbPath} (not created yet — created on first snapshot)`;
@@ -24536,7 +25104,7 @@ import {
24536
25104
  readFileSync as readFileSync8,
24537
25105
  realpathSync as realpathSync7,
24538
25106
  renameSync,
24539
- statSync as statSync6,
25107
+ statSync as statSync7,
24540
25108
  unlinkSync as unlinkSync2,
24541
25109
  watch,
24542
25110
  writeFileSync as writeFileSync2
@@ -25056,7 +25624,7 @@ function worktreeFileMetadata(path, knownSize) {
25056
25624
  if (!full)
25057
25625
  return {};
25058
25626
  try {
25059
- const stat3 = statSync6(full);
25627
+ const stat3 = statSync7(full);
25060
25628
  return {
25061
25629
  size: knownSize ?? stat3.size,
25062
25630
  created_at: isoDate(stat3.birthtimeMs),
@@ -25081,7 +25649,7 @@ async function directoryMetadata(target, path) {
25081
25649
  if (!full)
25082
25650
  return {};
25083
25651
  try {
25084
- const stat3 = statSync6(full);
25652
+ const stat3 = statSync7(full);
25085
25653
  return {
25086
25654
  created_at: isoDate(stat3.birthtimeMs),
25087
25655
  updated_at: isoDate(stat3.mtimeMs)
@@ -25358,7 +25926,7 @@ async function handleLog(url) {
25358
25926
  }
25359
25927
  function blamePathKey(p) {
25360
25928
  try {
25361
- const st = statSync6(join20(cwd, p));
25929
+ const st = statSync7(join20(cwd, p));
25362
25930
  return `${st.mtimeMs}:${st.size}`;
25363
25931
  } catch {
25364
25932
  return "missing";
@@ -25513,7 +26081,7 @@ async function handleFileDiff(url) {
25513
26081
  }
25514
26082
  function worktreeLineIndexSignature(full) {
25515
26083
  try {
25516
- const stat3 = statSync6(full);
26084
+ const stat3 = statSync7(full);
25517
26085
  return `size:${stat3.size}|mtime:${stat3.mtimeMs}|ctime:${stat3.ctimeMs}|ino:${stat3.ino || 0}`;
25518
26086
  } catch {
25519
26087
  return null;
@@ -25529,7 +26097,7 @@ async function getWorktreeLineIndex(full) {
25529
26097
  lineIndexCache.set(full, cached);
25530
26098
  return cached.index;
25531
26099
  }
25532
- const stat3 = statSync6(full);
26100
+ const stat3 = statSync7(full);
25533
26101
  if (stat3.size > LINE_INDEX_MAX_FILE_BYTES)
25534
26102
  return null;
25535
26103
  const index = await buildLineOffsetIndexFromStream(fileReadableStream(full), stat3.size);
@@ -25661,6 +26229,8 @@ async function handleFileRange(url) {
25661
26229
  const full = safeWorktreePath2(path);
25662
26230
  if (!full)
25663
26231
  return text("no file", 404);
26232
+ if (await rawFileSize(path, ref) == null)
26233
+ return text("no file", 404);
25664
26234
  const responseGeneration = generation;
25665
26235
  const result = await collectIndexedWorktreeLineRange(full, start, end);
25666
26236
  const body = {
@@ -25809,7 +26379,8 @@ async function rawFileSize(path, ref) {
25809
26379
  if (!full)
25810
26380
  return null;
25811
26381
  try {
25812
- return statSync6(full).size;
26382
+ const stats = statSync7(full);
26383
+ return stats.isFile() ? stats.size : null;
25813
26384
  } catch {
25814
26385
  return null;
25815
26386
  }
@@ -25870,7 +26441,7 @@ async function handleUploadFiles(req) {
25870
26441
  const realDir = safeOpenWorktreePath(dir);
25871
26442
  if (!realDir)
25872
26443
  return text("not found", 404);
25873
- const stats = statSync6(realDir);
26444
+ const stats = statSync7(realDir);
25874
26445
  if (!stats.isDirectory())
25875
26446
  return text("not a directory", 400);
25876
26447
  const files = form.getAll("files").filter((item) => item instanceof File);
@@ -26119,7 +26690,7 @@ async function handleOpenPath(req) {
26119
26690
  const target = safeOpenWorktreePath(targetPath);
26120
26691
  if (!target)
26121
26692
  return text("not found", 404);
26122
- const stats = statSync6(target);
26693
+ const stats = statSync7(target);
26123
26694
  if (!stats.isDirectory())
26124
26695
  return text("not a directory", 400);
26125
26696
  openOsPath(target);
@@ -26157,7 +26728,7 @@ async function handleTrashPath(req) {
26157
26728
  return text("not found", 404);
26158
26729
  let changedPaths;
26159
26730
  try {
26160
- const stats = statSync6(originalFullPath);
26731
+ const stats = statSync7(originalFullPath);
26161
26732
  if (!stats.isDirectory())
26162
26733
  changedPaths = [path];
26163
26734
  } catch {}
@@ -26210,7 +26781,7 @@ async function handleCreateDirectory(req) {
26210
26781
  const parent = safeOpenWorktreePath(dir);
26211
26782
  if (!parent)
26212
26783
  return text("not found", 404);
26213
- const stats = statSync6(parent);
26784
+ const stats = statSync7(parent);
26214
26785
  if (!stats.isDirectory())
26215
26786
  return text("not a directory", 400);
26216
26787
  const targetPath = dir ? `${dir}/${name}` : name;
@@ -26260,7 +26831,7 @@ async function handleRestoreTrash(req) {
26260
26831
  return text(restored.error || "undo failed", 409);
26261
26832
  let changedPaths;
26262
26833
  try {
26263
- const stats = statSync6(worktreePath(originalPath));
26834
+ const stats = statSync7(worktreePath(originalPath));
26264
26835
  if (!stats.isDirectory())
26265
26836
  changedPaths = [originalPath];
26266
26837
  } catch {}
@@ -27026,6 +27597,12 @@ data: ${watchLimitReached}
27026
27597
  root: cwd,
27027
27598
  started_at: new Date().toISOString()
27028
27599
  });
27600
+ process.on("uncaughtException", (error) => {
27601
+ console.error("[code-viewer] uncaught exception (server kept running):", error);
27602
+ });
27603
+ process.on("unhandledRejection", (reason) => {
27604
+ console.error("[code-viewer] unhandled rejection (server kept running):", reason);
27605
+ });
27029
27606
  process.on("exit", () => {
27030
27607
  removeServerRegistry(cwd, process.pid);
27031
27608
  closeSseClients();