@youtyan/code-viewer 0.8.9 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -18
- package/dist/code-viewer.js +753 -145
- package/package.json +1 -1
- package/skills/code-viewer-query/SKILL.md +3 -1
- package/web/app.js +307 -138
- package/web/style.css +28 -4
package/dist/code-viewer.js
CHANGED
|
@@ -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";
|
|
@@ -1167,8 +1167,12 @@ function runBytesAsync(args, cwd, options = {}) {
|
|
|
1167
1167
|
return new Promise((resolve) => {
|
|
1168
1168
|
const proc = spawn(args[0], args.slice(1), {
|
|
1169
1169
|
cwd,
|
|
1170
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1170
|
+
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
1171
1171
|
});
|
|
1172
|
+
if (options.stdin !== undefined) {
|
|
1173
|
+
proc.stdin?.on("error", () => {});
|
|
1174
|
+
proc.stdin?.end(options.stdin);
|
|
1175
|
+
}
|
|
1172
1176
|
const stdoutChunks = [];
|
|
1173
1177
|
const stderrChunks = [];
|
|
1174
1178
|
let stdoutBytes = 0;
|
|
@@ -1278,10 +1282,20 @@ function appendProcessError(stderr, err) {
|
|
|
1278
1282
|
return `${stderr}${stderr ? `
|
|
1279
1283
|
` : ""}${err.message}`;
|
|
1280
1284
|
}
|
|
1285
|
+
function assertReadableRegularFile(path) {
|
|
1286
|
+
const stats = statSync2(path);
|
|
1287
|
+
if (stats.isFile())
|
|
1288
|
+
return;
|
|
1289
|
+
const error = new Error(`not a regular file: ${path}`);
|
|
1290
|
+
error.code = stats.isDirectory() ? "EISDIR" : "EINVAL";
|
|
1291
|
+
throw error;
|
|
1292
|
+
}
|
|
1281
1293
|
function fileReadableStream(path) {
|
|
1294
|
+
assertReadableRegularFile(path);
|
|
1282
1295
|
return Readable.toWeb(createReadStream(path));
|
|
1283
1296
|
}
|
|
1284
1297
|
function fileByteRangeResponseBody(path, start, endInclusive) {
|
|
1298
|
+
assertReadableRegularFile(path);
|
|
1285
1299
|
return Readable.toWeb(createReadStream(path, { start, end: endInclusive }));
|
|
1286
1300
|
}
|
|
1287
1301
|
async function readFileTextRange(path, start, endExclusive) {
|
|
@@ -1415,7 +1429,7 @@ import {
|
|
|
1415
1429
|
readFileSync,
|
|
1416
1430
|
readlinkSync,
|
|
1417
1431
|
realpathSync as realpathSync2,
|
|
1418
|
-
statSync as
|
|
1432
|
+
statSync as statSync3
|
|
1419
1433
|
} from "node:fs";
|
|
1420
1434
|
import { open, stat } from "node:fs/promises";
|
|
1421
1435
|
import { dirname as dirname3, join as join4, posix, relative as relative2 } from "node:path";
|
|
@@ -1438,9 +1452,10 @@ function run(args, cwd) {
|
|
|
1438
1452
|
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1439
1453
|
});
|
|
1440
1454
|
}
|
|
1441
|
-
function runGitAsync(args, cwd) {
|
|
1455
|
+
function runGitAsync(args, cwd, options = {}) {
|
|
1442
1456
|
return runAsync(resolveGitArgs(args), cwd, {
|
|
1443
|
-
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1457
|
+
timeout: GIT_COMMAND_TIMEOUT_MS,
|
|
1458
|
+
...options
|
|
1444
1459
|
});
|
|
1445
1460
|
}
|
|
1446
1461
|
function resolveGitArgs(args) {
|
|
@@ -1520,7 +1535,7 @@ async function repoStatusMapAsync(cwd, now = Date.now()) {
|
|
|
1520
1535
|
"status",
|
|
1521
1536
|
"--porcelain=v1",
|
|
1522
1537
|
"-z",
|
|
1523
|
-
"--untracked-files=
|
|
1538
|
+
"--untracked-files=normal"
|
|
1524
1539
|
], cwd);
|
|
1525
1540
|
if (res.code !== 0)
|
|
1526
1541
|
return map;
|
|
@@ -1532,7 +1547,7 @@ async function repoStatusMapAsync(cwd, now = Date.now()) {
|
|
|
1532
1547
|
if (!path)
|
|
1533
1548
|
continue;
|
|
1534
1549
|
if (xy === "??") {
|
|
1535
|
-
map.set(path, "
|
|
1550
|
+
map.set(path, "U");
|
|
1536
1551
|
continue;
|
|
1537
1552
|
}
|
|
1538
1553
|
if (xy[0] === "R" || xy[0] === "C" || xy[1] === "R" || xy[1] === "C") {
|
|
@@ -1547,6 +1562,27 @@ async function repoStatusMapAsync(cwd, now = Date.now()) {
|
|
|
1547
1562
|
setTimedCacheEntry(repoStatusMapCache, cwd, { map }, now);
|
|
1548
1563
|
return map;
|
|
1549
1564
|
}
|
|
1565
|
+
function repoStatusForPath(map, path) {
|
|
1566
|
+
const own = map.get(path) ?? map.get(`${path}/`);
|
|
1567
|
+
if (own)
|
|
1568
|
+
return { code: own, inherited: false };
|
|
1569
|
+
for (let slash = path.lastIndexOf("/");slash > 0; slash = path.lastIndexOf("/", slash - 1)) {
|
|
1570
|
+
const ancestor = map.get(`${path.slice(0, slash)}/`);
|
|
1571
|
+
if (ancestor)
|
|
1572
|
+
return { code: ancestor, inherited: true };
|
|
1573
|
+
}
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
async function ignoredPathsAsync(paths, cwd) {
|
|
1577
|
+
if (!paths.length)
|
|
1578
|
+
return new Set;
|
|
1579
|
+
const res = await runGitAsync(["git", "check-ignore", "-z", "--stdin"], cwd, {
|
|
1580
|
+
stdin: paths.join("\x00")
|
|
1581
|
+
});
|
|
1582
|
+
if (res.code !== 0 && res.code !== 1)
|
|
1583
|
+
return new Set;
|
|
1584
|
+
return new Set(res.stdout.split("\x00").filter(Boolean));
|
|
1585
|
+
}
|
|
1550
1586
|
function showAsync(ref, path, cwd) {
|
|
1551
1587
|
return runGitAsync(["git", "show", `${ref}:${path}`], cwd);
|
|
1552
1588
|
}
|
|
@@ -2060,7 +2096,7 @@ function isGitInternalPath(path) {
|
|
|
2060
2096
|
function syntheticUncommittedBlameFromWorktree(cwd, path) {
|
|
2061
2097
|
const filePath = join4(cwd, path);
|
|
2062
2098
|
try {
|
|
2063
|
-
const stat2 =
|
|
2099
|
+
const stat2 = statSync3(filePath);
|
|
2064
2100
|
if (!stat2.isFile())
|
|
2065
2101
|
return { lines: [], commits: {}, error: "not a file" };
|
|
2066
2102
|
const text = readFileSync(filePath, "utf8");
|
|
@@ -2250,7 +2286,7 @@ function resolveWorktreeSymlinkTarget(cwd, full) {
|
|
|
2250
2286
|
let symlink_target_type = "missing";
|
|
2251
2287
|
if (realpathWithinRepo(cwd, full, false) !== null) {
|
|
2252
2288
|
try {
|
|
2253
|
-
const stat2 =
|
|
2289
|
+
const stat2 = statSync3(full);
|
|
2254
2290
|
symlink_target_type = stat2.isDirectory() ? "tree" : stat2.isFile() ? "blob" : "missing";
|
|
2255
2291
|
} catch {
|
|
2256
2292
|
symlink_target_type = "missing";
|
|
@@ -3982,7 +4018,7 @@ __export(exports_file_cli, {
|
|
|
3982
4018
|
FILE_DEFAULT_HISTORY_LIMIT: () => FILE_DEFAULT_HISTORY_LIMIT,
|
|
3983
4019
|
FILE_AGENT_HELP: () => FILE_AGENT_HELP
|
|
3984
4020
|
});
|
|
3985
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync4, statSync as
|
|
4021
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
3986
4022
|
import { join as join6, relative as relative3 } from "node:path";
|
|
3987
4023
|
function validatePath(value) {
|
|
3988
4024
|
return validateRepoRelativePathValue(value, "--path");
|
|
@@ -4381,7 +4417,7 @@ async function readShowTextAsync(root, command) {
|
|
|
4381
4417
|
};
|
|
4382
4418
|
}
|
|
4383
4419
|
try {
|
|
4384
|
-
const stat2 =
|
|
4420
|
+
const stat2 = statSync4(full);
|
|
4385
4421
|
if (!stat2.isFile()) {
|
|
4386
4422
|
return { code: 1, stdout: "", stderr: "not a file" };
|
|
4387
4423
|
}
|
|
@@ -10531,6 +10567,38 @@ var init_sql_capture = __esm(() => {
|
|
|
10531
10567
|
storage = new AsyncLocalStorage;
|
|
10532
10568
|
});
|
|
10533
10569
|
|
|
10570
|
+
// web-src/server/database/adapters/table-meta-cache.ts
|
|
10571
|
+
function createTableMetaCache(now = () => Date.now()) {
|
|
10572
|
+
const columns = new Map;
|
|
10573
|
+
const rowCounts = new Map;
|
|
10574
|
+
async function readThrough(store, table, ttlMs, load) {
|
|
10575
|
+
const cached = store.get(table);
|
|
10576
|
+
if (cached && cached.expires > now())
|
|
10577
|
+
return cached.value;
|
|
10578
|
+
const value = await load();
|
|
10579
|
+
store.set(table, { value, expires: now() + ttlMs });
|
|
10580
|
+
return value;
|
|
10581
|
+
}
|
|
10582
|
+
return {
|
|
10583
|
+
getColumns(table, load) {
|
|
10584
|
+
return readThrough(columns, table, COLUMNS_TTL_MS, load);
|
|
10585
|
+
},
|
|
10586
|
+
getRowCount(table, load) {
|
|
10587
|
+
return readThrough(rowCounts, table, ROWCOUNT_TTL_MS, load);
|
|
10588
|
+
},
|
|
10589
|
+
invalidate(table) {
|
|
10590
|
+
if (table) {
|
|
10591
|
+
columns.delete(table);
|
|
10592
|
+
rowCounts.delete(table);
|
|
10593
|
+
return;
|
|
10594
|
+
}
|
|
10595
|
+
columns.clear();
|
|
10596
|
+
rowCounts.clear();
|
|
10597
|
+
}
|
|
10598
|
+
};
|
|
10599
|
+
}
|
|
10600
|
+
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000;
|
|
10601
|
+
|
|
10534
10602
|
// web-src/server/database/adapters/docker.ts
|
|
10535
10603
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
10536
10604
|
import mysql from "mysql2/promise";
|
|
@@ -10966,39 +11034,6 @@ function buildTableSelectList(columns, kind) {
|
|
|
10966
11034
|
});
|
|
10967
11035
|
return hasSpatialColumn ? parts.join(", ") : "*";
|
|
10968
11036
|
}
|
|
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
11037
|
function observeBackgroundRejection(promise) {
|
|
11003
11038
|
promise.catch(() => {
|
|
11004
11039
|
return;
|
|
@@ -11618,7 +11653,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
11618
11653
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
11619
11654
|
});
|
|
11620
11655
|
}
|
|
11621
|
-
var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config),
|
|
11656
|
+
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
11657
|
var init_docker = __esm(() => {
|
|
11623
11658
|
init_mutate();
|
|
11624
11659
|
init_sql_snapshot();
|
|
@@ -13860,6 +13895,72 @@ var init_s3 = __esm(() => {
|
|
|
13860
13895
|
s3DockerCurlTimeoutMs = DEFAULT_S3_DOCKER_CURL_TIMEOUT_MS;
|
|
13861
13896
|
});
|
|
13862
13897
|
|
|
13898
|
+
// web-src/server/database/adapters/sqlite-introspection.ts
|
|
13899
|
+
function sqliteTableInfoSql(table) {
|
|
13900
|
+
return `PRAGMA table_info(${sanitizeIdentifier(table)})`;
|
|
13901
|
+
}
|
|
13902
|
+
function sqliteIndexListSql(table) {
|
|
13903
|
+
return `PRAGMA index_list(${sanitizeIdentifier(table)})`;
|
|
13904
|
+
}
|
|
13905
|
+
function sqliteIndexInfoSql(index) {
|
|
13906
|
+
return `PRAGMA index_info(${sanitizeIdentifier(index)})`;
|
|
13907
|
+
}
|
|
13908
|
+
function sqliteForeignKeyListSql(table) {
|
|
13909
|
+
return `PRAGMA foreign_key_list(${sanitizeIdentifier(table)})`;
|
|
13910
|
+
}
|
|
13911
|
+
function sqliteColumnFromPragmaRow(row) {
|
|
13912
|
+
return {
|
|
13913
|
+
name: row.name,
|
|
13914
|
+
type: row.type || "TEXT",
|
|
13915
|
+
nullable: row.notnull === 0,
|
|
13916
|
+
primaryKey: row.pk > 0,
|
|
13917
|
+
defaultValue: row.dflt_value
|
|
13918
|
+
};
|
|
13919
|
+
}
|
|
13920
|
+
function sqliteTableInfoFromRow(row) {
|
|
13921
|
+
return {
|
|
13922
|
+
name: row.name,
|
|
13923
|
+
type: row.type,
|
|
13924
|
+
rowCount: null
|
|
13925
|
+
};
|
|
13926
|
+
}
|
|
13927
|
+
function sqliteRowCountUnionSql(tables) {
|
|
13928
|
+
return tables.map((table) => `SELECT ${escapeSqlString(table)} AS tbl, COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}`).join(" UNION ALL ");
|
|
13929
|
+
}
|
|
13930
|
+
function sqliteRowCountSql(table) {
|
|
13931
|
+
return `SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}`;
|
|
13932
|
+
}
|
|
13933
|
+
function assertReadonlySqliteStatement(sql) {
|
|
13934
|
+
const upper = sql.trim().toUpperCase();
|
|
13935
|
+
if (!SQLITE_READONLY_FIRST_WORDS.has(upper.split(/\s/)[0])) {
|
|
13936
|
+
throw new Error("Only SELECT, PRAGMA, EXPLAIN, and WITH queries are allowed");
|
|
13937
|
+
}
|
|
13938
|
+
if (SQLITE_BLOCKED_KEYWORDS_RE.test(upper)) {
|
|
13939
|
+
throw new Error("Query contains a disallowed statement keyword");
|
|
13940
|
+
}
|
|
13941
|
+
}
|
|
13942
|
+
function stripTrailingSemicolon(sql) {
|
|
13943
|
+
return sql.trim().replace(/;\s*$/, "");
|
|
13944
|
+
}
|
|
13945
|
+
var SQLITE_INTROSPECTION_SQL, SQLITE_READONLY_FIRST_WORDS, SQLITE_BLOCKED_KEYWORDS_RE;
|
|
13946
|
+
var init_sqlite_introspection = __esm(() => {
|
|
13947
|
+
init_sql_utils();
|
|
13948
|
+
SQLITE_INTROSPECTION_SQL = {
|
|
13949
|
+
listTables: "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
|
13950
|
+
listIndexes: "SELECT name, tbl_name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
|
13951
|
+
listForeignKeyTables: "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND sql NOT LIKE '%VIRTUAL%' ORDER BY name",
|
|
13952
|
+
createStatement: "SELECT sql FROM sqlite_master WHERE name = ?",
|
|
13953
|
+
triggers: "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?"
|
|
13954
|
+
};
|
|
13955
|
+
SQLITE_READONLY_FIRST_WORDS = new Set([
|
|
13956
|
+
"SELECT",
|
|
13957
|
+
"PRAGMA",
|
|
13958
|
+
"EXPLAIN",
|
|
13959
|
+
"WITH"
|
|
13960
|
+
]);
|
|
13961
|
+
SQLITE_BLOCKED_KEYWORDS_RE = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|DETACH|REPLACE|VACUUM|REINDEX|LOAD_EXTENSION)\b/;
|
|
13962
|
+
});
|
|
13963
|
+
|
|
13863
13964
|
// web-src/server/database/adapters/sqlite.ts
|
|
13864
13965
|
function safePrepare(db, sql) {
|
|
13865
13966
|
const stmt = db.prepare(sql);
|
|
@@ -13886,14 +13987,8 @@ function queryRowsToResult(rows, columns) {
|
|
|
13886
13987
|
};
|
|
13887
13988
|
}
|
|
13888
13989
|
function queryColumns(db, table) {
|
|
13889
|
-
const rows = db.prepare(
|
|
13890
|
-
return rows.map(
|
|
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
|
-
}));
|
|
13990
|
+
const rows = db.prepare(sqliteTableInfoSql(table)).all();
|
|
13991
|
+
return rows.map(sqliteColumnFromPragmaRow);
|
|
13897
13992
|
}
|
|
13898
13993
|
function wrapDbWithSqlCapture(rawDb) {
|
|
13899
13994
|
return new Proxy(rawDb, {
|
|
@@ -13924,12 +14019,8 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13924
14019
|
model: "sql",
|
|
13925
14020
|
capabilities: { snapshot: true },
|
|
13926
14021
|
getTables() {
|
|
13927
|
-
const rows = db.prepare(
|
|
13928
|
-
return rows.map(
|
|
13929
|
-
name: row.name,
|
|
13930
|
-
type: row.type,
|
|
13931
|
-
rowCount: null
|
|
13932
|
-
}));
|
|
14022
|
+
const rows = db.prepare(SQLITE_INTROSPECTION_SQL.listTables).all();
|
|
14023
|
+
return rows.map(sqliteTableInfoFromRow);
|
|
13933
14024
|
},
|
|
13934
14025
|
async getTablesAsync() {
|
|
13935
14026
|
return this.getTables();
|
|
@@ -13941,10 +14032,10 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13941
14032
|
return this.getColumns(table);
|
|
13942
14033
|
},
|
|
13943
14034
|
getIndexes() {
|
|
13944
|
-
const rows = db.prepare(
|
|
14035
|
+
const rows = db.prepare(SQLITE_INTROSPECTION_SQL.listIndexes).all();
|
|
13945
14036
|
return rows.map((row) => {
|
|
13946
|
-
const info = db.prepare(
|
|
13947
|
-
const indexList = db.prepare(
|
|
14037
|
+
const info = db.prepare(sqliteIndexInfoSql(row.name)).all();
|
|
14038
|
+
const indexList = db.prepare(sqliteIndexListSql(row.tbl_name)).all();
|
|
13948
14039
|
const entry = indexList.find((i) => i.name === row.name);
|
|
13949
14040
|
return {
|
|
13950
14041
|
name: row.name,
|
|
@@ -13958,11 +14049,11 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13958
14049
|
return this.getIndexes();
|
|
13959
14050
|
},
|
|
13960
14051
|
getForeignKeys() {
|
|
13961
|
-
const tables = db.prepare(
|
|
14052
|
+
const tables = db.prepare(SQLITE_INTROSPECTION_SQL.listForeignKeyTables).all();
|
|
13962
14053
|
const fks = [];
|
|
13963
14054
|
for (const t of tables) {
|
|
13964
14055
|
try {
|
|
13965
|
-
const rows = db.prepare(
|
|
14056
|
+
const rows = db.prepare(sqliteForeignKeyListSql(t.name)).all();
|
|
13966
14057
|
for (const row of rows) {
|
|
13967
14058
|
fks.push({
|
|
13968
14059
|
fromTable: t.name,
|
|
@@ -13989,7 +14080,7 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13989
14080
|
return this.getColumnsMulti(tables);
|
|
13990
14081
|
},
|
|
13991
14082
|
getTableRowCount(table) {
|
|
13992
|
-
const row = db.prepare(
|
|
14083
|
+
const row = db.prepare(sqliteRowCountSql(table)).get();
|
|
13993
14084
|
return row?.cnt ?? 0;
|
|
13994
14085
|
},
|
|
13995
14086
|
async getTableRowCountAsync(table) {
|
|
@@ -13999,16 +14090,14 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13999
14090
|
const result = new Map;
|
|
14000
14091
|
if (tables.length === 0)
|
|
14001
14092
|
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
14093
|
try {
|
|
14005
|
-
const rows = db.prepare(
|
|
14094
|
+
const rows = db.prepare(sqliteRowCountUnionSql(tables)).all();
|
|
14006
14095
|
for (const row of rows) {
|
|
14007
14096
|
result.set(row.tbl, row.cnt);
|
|
14008
14097
|
}
|
|
14009
14098
|
} catch {
|
|
14010
14099
|
for (const t of tables) {
|
|
14011
|
-
const row = db.prepare(
|
|
14100
|
+
const row = db.prepare(sqliteRowCountSql(t)).get();
|
|
14012
14101
|
result.set(t, row?.cnt ?? 0);
|
|
14013
14102
|
}
|
|
14014
14103
|
}
|
|
@@ -14060,17 +14149,8 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
14060
14149
|
};
|
|
14061
14150
|
},
|
|
14062
14151
|
executeReadonlyQuery(sql, params, maxRows = 1000) {
|
|
14063
|
-
|
|
14064
|
-
const
|
|
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*$/, "");
|
|
14152
|
+
assertReadonlySqliteStatement(sql);
|
|
14153
|
+
const limited = stripTrailingSemicolon(sql);
|
|
14074
14154
|
const wrappedSql = `SELECT * FROM (${limited}) LIMIT ${maxRows + 1}`;
|
|
14075
14155
|
let rows;
|
|
14076
14156
|
try {
|
|
@@ -14101,14 +14181,14 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
14101
14181
|
return this.executeReadonlyQuery(sql, params, maxRows);
|
|
14102
14182
|
},
|
|
14103
14183
|
getCreateStatement(table) {
|
|
14104
|
-
const row = db.prepare(
|
|
14184
|
+
const row = db.prepare(SQLITE_INTROSPECTION_SQL.createStatement).get(table);
|
|
14105
14185
|
return row?.sql ?? "";
|
|
14106
14186
|
},
|
|
14107
14187
|
async getCreateStatementAsync(table) {
|
|
14108
14188
|
return this.getCreateStatement(table);
|
|
14109
14189
|
},
|
|
14110
14190
|
getTriggers(table) {
|
|
14111
|
-
const rows = db.prepare(
|
|
14191
|
+
const rows = db.prepare(SQLITE_INTROSPECTION_SQL.triggers).all(table);
|
|
14112
14192
|
return rows.map((row) => ({ name: row.name, sql: row.sql ?? "" }));
|
|
14113
14193
|
},
|
|
14114
14194
|
async getTriggersAsync(table) {
|
|
@@ -14188,6 +14268,7 @@ var init_sqlite = __esm(() => {
|
|
|
14188
14268
|
init_sql_utils();
|
|
14189
14269
|
init_sqlite_driver();
|
|
14190
14270
|
init_sql_capture();
|
|
14271
|
+
init_sqlite_introspection();
|
|
14191
14272
|
sqliteAdapterFactory = {
|
|
14192
14273
|
async open(path) {
|
|
14193
14274
|
const DbClass = await loadSqliteClass();
|
|
@@ -14220,13 +14301,13 @@ import {
|
|
|
14220
14301
|
openSync,
|
|
14221
14302
|
readSync,
|
|
14222
14303
|
realpathSync as realpathSync5,
|
|
14223
|
-
statSync as
|
|
14304
|
+
statSync as statSync5
|
|
14224
14305
|
} from "node:fs";
|
|
14225
14306
|
import { lstat, open as open2, readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
14226
14307
|
import { basename, join as join9, relative as relative4 } from "node:path";
|
|
14227
14308
|
function isSqliteFile(fullPath) {
|
|
14228
14309
|
try {
|
|
14229
|
-
const stat3 =
|
|
14310
|
+
const stat3 = statSync5(fullPath);
|
|
14230
14311
|
if (!stat3.isFile() || stat3.size < 16)
|
|
14231
14312
|
return false;
|
|
14232
14313
|
const buf = Buffer.alloc(16);
|
|
@@ -14424,6 +14505,8 @@ function dbKindDisplayName(kind) {
|
|
|
14424
14505
|
return "MySQL";
|
|
14425
14506
|
case "sqlite":
|
|
14426
14507
|
return "SQLite";
|
|
14508
|
+
case "d1":
|
|
14509
|
+
return "Cloudflare D1";
|
|
14427
14510
|
}
|
|
14428
14511
|
}
|
|
14429
14512
|
function detectDbKindFromContainerPort(port) {
|
|
@@ -15850,6 +15933,310 @@ function asAsyncDoc(source) {
|
|
|
15850
15933
|
};
|
|
15851
15934
|
}
|
|
15852
15935
|
|
|
15936
|
+
// web-src/server/database/adapters/d1.ts
|
|
15937
|
+
function isD1InternalName(name) {
|
|
15938
|
+
return D1_INTERNAL_NAME_RE.test(name);
|
|
15939
|
+
}
|
|
15940
|
+
function isD1HttpError(err) {
|
|
15941
|
+
return err instanceof D1HttpError;
|
|
15942
|
+
}
|
|
15943
|
+
function d1ErrorMessage(envelope, status, sql) {
|
|
15944
|
+
const first = envelope.errors?.find((entry) => entry?.message);
|
|
15945
|
+
const detail = first?.message?.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
15946
|
+
const statement = sql.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
15947
|
+
return `${detail || `D1 HTTP ${status}`} (sql: ${statement})`;
|
|
15948
|
+
}
|
|
15949
|
+
function toDbValue(value) {
|
|
15950
|
+
if (value === null || value === undefined)
|
|
15951
|
+
return null;
|
|
15952
|
+
if (typeof value === "number" || typeof value === "string")
|
|
15953
|
+
return value;
|
|
15954
|
+
if (typeof value === "boolean")
|
|
15955
|
+
return value;
|
|
15956
|
+
if (Array.isArray(value)) {
|
|
15957
|
+
return value.every((byte) => typeof byte === "number") ? new Uint8Array(value) : JSON.stringify(value);
|
|
15958
|
+
}
|
|
15959
|
+
if (typeof value === "object")
|
|
15960
|
+
return JSON.stringify(value);
|
|
15961
|
+
return String(value);
|
|
15962
|
+
}
|
|
15963
|
+
function parseRawResult(result) {
|
|
15964
|
+
const columns = Array.isArray(result?.results?.columns) ? result.results.columns.map((name) => String(name)) : [];
|
|
15965
|
+
const rawRows = Array.isArray(result?.results?.rows) ? result.results.rows : [];
|
|
15966
|
+
const rows = rawRows.map((row) => Array.isArray(row) ? row.map(toDbValue) : []);
|
|
15967
|
+
return {
|
|
15968
|
+
columns,
|
|
15969
|
+
columnTypes: columns.map(() => "TEXT"),
|
|
15970
|
+
rows,
|
|
15971
|
+
rowCount: rows.length
|
|
15972
|
+
};
|
|
15973
|
+
}
|
|
15974
|
+
async function d1Fetch(url, init, signal) {
|
|
15975
|
+
if (signal?.aborted)
|
|
15976
|
+
throw new D1HttpError(503, "D1 request aborted");
|
|
15977
|
+
const controller = new AbortController;
|
|
15978
|
+
const onParentAbort = () => controller.abort();
|
|
15979
|
+
signal?.addEventListener("abort", onParentAbort, { once: true });
|
|
15980
|
+
const timer = setTimeout(() => controller.abort(), d1RequestTimeoutMs);
|
|
15981
|
+
try {
|
|
15982
|
+
const d1FetchImpl = d1FetchOverride ?? globalThis.fetch;
|
|
15983
|
+
return await d1FetchImpl(url, { ...init, signal: controller.signal });
|
|
15984
|
+
} catch (err) {
|
|
15985
|
+
if (signal?.aborted)
|
|
15986
|
+
throw new D1HttpError(503, "D1 request aborted");
|
|
15987
|
+
if (controller.signal.aborted) {
|
|
15988
|
+
throw new D1HttpError(503, `D1 request timed out after ${d1RequestTimeoutMs}ms`);
|
|
15989
|
+
}
|
|
15990
|
+
throw new D1HttpError(503, `D1 request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15991
|
+
} finally {
|
|
15992
|
+
clearTimeout(timer);
|
|
15993
|
+
signal?.removeEventListener("abort", onParentAbort);
|
|
15994
|
+
}
|
|
15995
|
+
}
|
|
15996
|
+
function createD1Adapter(config) {
|
|
15997
|
+
const baseUrl = (config.apiBaseUrl || DEFAULT_D1_API_BASE_URL).replace(/\/$/, "");
|
|
15998
|
+
const rawUrl = `${baseUrl}/accounts/${encodeURIComponent(config.accountId)}/d1/database/${encodeURIComponent(config.databaseId)}/raw`;
|
|
15999
|
+
const tableMetaCache = createTableMetaCache();
|
|
16000
|
+
async function runSql(sql, params = [], signal) {
|
|
16001
|
+
if (!config.apiToken) {
|
|
16002
|
+
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).");
|
|
16003
|
+
}
|
|
16004
|
+
recordSql(sql);
|
|
16005
|
+
const res = await d1Fetch(rawUrl, {
|
|
16006
|
+
method: "POST",
|
|
16007
|
+
headers: {
|
|
16008
|
+
"Content-Type": "application/json",
|
|
16009
|
+
Authorization: `Bearer ${config.apiToken}`
|
|
16010
|
+
},
|
|
16011
|
+
body: JSON.stringify({ sql, params })
|
|
16012
|
+
}, signal);
|
|
16013
|
+
let envelope;
|
|
16014
|
+
try {
|
|
16015
|
+
envelope = await res.json();
|
|
16016
|
+
} catch {
|
|
16017
|
+
throw new D1HttpError(res.status, `D1 HTTP ${res.status}: invalid JSON`);
|
|
16018
|
+
}
|
|
16019
|
+
if (!res.ok || envelope.success === false) {
|
|
16020
|
+
const status = res.ok ? 400 : res.status;
|
|
16021
|
+
throw new D1HttpError(status, d1ErrorMessage(envelope, status, sql));
|
|
16022
|
+
}
|
|
16023
|
+
return parseRawResult(envelope.result?.[0]);
|
|
16024
|
+
}
|
|
16025
|
+
async function runSqlRecords(sql, params = [], signal) {
|
|
16026
|
+
const result = await runSql(sql, params, signal);
|
|
16027
|
+
return result.rows.map((row) => {
|
|
16028
|
+
const record = {};
|
|
16029
|
+
result.columns.forEach((column, index) => {
|
|
16030
|
+
record[column] = row[index] ?? null;
|
|
16031
|
+
});
|
|
16032
|
+
return record;
|
|
16033
|
+
});
|
|
16034
|
+
}
|
|
16035
|
+
async function loadColumns(table, signal) {
|
|
16036
|
+
const rows = await runSqlRecords(sqliteTableInfoSql(table), [], signal);
|
|
16037
|
+
return rows.map(sqliteColumnFromPragmaRow);
|
|
16038
|
+
}
|
|
16039
|
+
function getColumnsAsync(table, signal) {
|
|
16040
|
+
return tableMetaCache.getColumns(table, () => loadColumns(table, signal));
|
|
16041
|
+
}
|
|
16042
|
+
async function countRows(table, signal) {
|
|
16043
|
+
const result = await runSql(sqliteRowCountSql(table), [], signal);
|
|
16044
|
+
return Number(result.rows[0]?.[0] ?? 0);
|
|
16045
|
+
}
|
|
16046
|
+
async function selectPage(table, columns, options, signal) {
|
|
16047
|
+
const order = buildOrderClause(filterOrderByColumns(options.orderBy, columns.map((column) => column.name)));
|
|
16048
|
+
const whereClause = options.where ? ` WHERE ${options.where}` : "";
|
|
16049
|
+
return runSql(`SELECT * FROM ${sanitizeIdentifier(table)}${whereClause}${order} LIMIT ? OFFSET ?`, [...options.params ?? [], options.limit, options.offset], signal);
|
|
16050
|
+
}
|
|
16051
|
+
const adapter = {
|
|
16052
|
+
kind: "d1",
|
|
16053
|
+
model: "sql",
|
|
16054
|
+
capabilities: { snapshot: true },
|
|
16055
|
+
async getTablesAsync(signal) {
|
|
16056
|
+
const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listTables, [], signal);
|
|
16057
|
+
return rows.filter((row) => !isD1InternalName(row.name)).map(sqliteTableInfoFromRow);
|
|
16058
|
+
},
|
|
16059
|
+
getColumnsAsync,
|
|
16060
|
+
async getColumnsMultiAsync(tables, signal) {
|
|
16061
|
+
const entries = await Promise.all(tables.map(async (table) => [table, await getColumnsAsync(table, signal)]));
|
|
16062
|
+
return new Map(entries);
|
|
16063
|
+
},
|
|
16064
|
+
async getIndexesAsync(signal) {
|
|
16065
|
+
const indexes = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listIndexes, [], signal);
|
|
16066
|
+
const described = await Promise.all(indexes.filter((index) => !isD1InternalName(index.name) && !isD1InternalName(index.tbl_name)).map(async (index) => {
|
|
16067
|
+
try {
|
|
16068
|
+
const [info, indexList] = await Promise.all([
|
|
16069
|
+
runSqlRecords(sqliteIndexInfoSql(index.name), [], signal),
|
|
16070
|
+
runSqlRecords(sqliteIndexListSql(index.tbl_name), [], signal)
|
|
16071
|
+
]);
|
|
16072
|
+
const entry = indexList.find((row) => row.name === index.name);
|
|
16073
|
+
return {
|
|
16074
|
+
name: index.name,
|
|
16075
|
+
table: index.tbl_name,
|
|
16076
|
+
columns: info.map((row) => row.name),
|
|
16077
|
+
unique: entry ? Number(entry.unique) === 1 : false
|
|
16078
|
+
};
|
|
16079
|
+
} catch (err) {
|
|
16080
|
+
if (isAbortLikeError(err, signal))
|
|
16081
|
+
throw err;
|
|
16082
|
+
return null;
|
|
16083
|
+
}
|
|
16084
|
+
}));
|
|
16085
|
+
return described.filter((index) => index !== null);
|
|
16086
|
+
},
|
|
16087
|
+
async getForeignKeysAsync(signal) {
|
|
16088
|
+
const tables = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listForeignKeyTables, [], signal);
|
|
16089
|
+
const perTable = await Promise.all(tables.filter((table) => !isD1InternalName(table.name)).map(async (table) => {
|
|
16090
|
+
try {
|
|
16091
|
+
const rows = await runSqlRecords(sqliteForeignKeyListSql(table.name), [], signal);
|
|
16092
|
+
return rows.map((row) => ({
|
|
16093
|
+
fromTable: table.name,
|
|
16094
|
+
fromColumn: row.from,
|
|
16095
|
+
toTable: row.table,
|
|
16096
|
+
toColumn: row.to
|
|
16097
|
+
}));
|
|
16098
|
+
} catch (err) {
|
|
16099
|
+
if (isAbortLikeError(err, signal))
|
|
16100
|
+
throw err;
|
|
16101
|
+
return [];
|
|
16102
|
+
}
|
|
16103
|
+
}));
|
|
16104
|
+
return perTable.flat();
|
|
16105
|
+
},
|
|
16106
|
+
getTableRowCountAsync(table, signal) {
|
|
16107
|
+
return tableMetaCache.getRowCount(table, () => countRows(table, signal));
|
|
16108
|
+
},
|
|
16109
|
+
async getTableRowCountsAsync(tables, signal) {
|
|
16110
|
+
const result = new Map;
|
|
16111
|
+
const countable = tables.filter((table) => !isD1InternalName(table));
|
|
16112
|
+
if (countable.length === 0)
|
|
16113
|
+
return result;
|
|
16114
|
+
try {
|
|
16115
|
+
const rows = await runSqlRecords(sqliteRowCountUnionSql(countable), [], signal);
|
|
16116
|
+
for (const row of rows)
|
|
16117
|
+
result.set(String(row.tbl), Number(row.cnt));
|
|
16118
|
+
return result;
|
|
16119
|
+
} catch {
|
|
16120
|
+
for (const table of countable) {
|
|
16121
|
+
try {
|
|
16122
|
+
result.set(table, await countRows(table, signal));
|
|
16123
|
+
} catch (err) {
|
|
16124
|
+
if (isAbortLikeError(err, signal))
|
|
16125
|
+
throw err;
|
|
16126
|
+
}
|
|
16127
|
+
}
|
|
16128
|
+
return result;
|
|
16129
|
+
}
|
|
16130
|
+
},
|
|
16131
|
+
async getTablePageAsync(table, options, signal) {
|
|
16132
|
+
const columns = await getColumnsAsync(table, signal);
|
|
16133
|
+
return selectPage(table, columns, options, signal);
|
|
16134
|
+
},
|
|
16135
|
+
async getTablePageWithMeta(table, options, signal) {
|
|
16136
|
+
const columns = await getColumnsAsync(table, signal);
|
|
16137
|
+
const [page, totalRows] = await Promise.all([
|
|
16138
|
+
selectPage(table, columns, options, signal),
|
|
16139
|
+
countRows(table, signal)
|
|
16140
|
+
]);
|
|
16141
|
+
return {
|
|
16142
|
+
columns,
|
|
16143
|
+
rows: page.rows,
|
|
16144
|
+
rowCount: page.rowCount,
|
|
16145
|
+
totalRows
|
|
16146
|
+
};
|
|
16147
|
+
},
|
|
16148
|
+
async getFilteredTablePageWithMeta(table, options, signal) {
|
|
16149
|
+
const columns = await getColumnsAsync(table, signal);
|
|
16150
|
+
const columnNames = columns.map((column) => column.name);
|
|
16151
|
+
const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite", filterExactColumns(options.exact, columnNames));
|
|
16152
|
+
const whereClause = filter.where ? ` WHERE ${filter.where}` : "";
|
|
16153
|
+
const [page, countResult] = await Promise.all([
|
|
16154
|
+
selectPage(table, columns, { ...options, where: filter.where, params: filter.params }, signal),
|
|
16155
|
+
runSql(`SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}${whereClause}`, filter.params, signal)
|
|
16156
|
+
]);
|
|
16157
|
+
return {
|
|
16158
|
+
columns,
|
|
16159
|
+
rows: page.rows,
|
|
16160
|
+
rowCount: page.rowCount,
|
|
16161
|
+
totalRows: Number(countResult.rows[0]?.[0] ?? 0)
|
|
16162
|
+
};
|
|
16163
|
+
},
|
|
16164
|
+
async executeReadonlyQueryAsync(sql, params, maxRows = 1000, signal) {
|
|
16165
|
+
assertReadonlySqliteStatement(sql);
|
|
16166
|
+
const limited = stripTrailingSemicolon(sql);
|
|
16167
|
+
let result;
|
|
16168
|
+
try {
|
|
16169
|
+
result = await runSql(`SELECT * FROM (${limited}) LIMIT ${maxRows + 1}`, params, signal);
|
|
16170
|
+
} catch (wrapErr) {
|
|
16171
|
+
try {
|
|
16172
|
+
result = await runSql(`${limited} LIMIT ${maxRows + 1}`, params, signal);
|
|
16173
|
+
} catch {
|
|
16174
|
+
throw wrapErr;
|
|
16175
|
+
}
|
|
16176
|
+
}
|
|
16177
|
+
return result.rows.length > maxRows ? { ...result, rows: result.rows.slice(0, maxRows), rowCount: maxRows } : result;
|
|
16178
|
+
},
|
|
16179
|
+
invalidateTableMetaCache(table) {
|
|
16180
|
+
tableMetaCache.invalidate(table);
|
|
16181
|
+
},
|
|
16182
|
+
async getCreateStatementAsync(table, signal) {
|
|
16183
|
+
const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.createStatement, [table], signal);
|
|
16184
|
+
return rows[0]?.sql ?? "";
|
|
16185
|
+
},
|
|
16186
|
+
async getTriggersAsync(table, signal) {
|
|
16187
|
+
const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.triggers, [table], signal);
|
|
16188
|
+
return rows.map((row) => ({ name: row.name, sql: row.sql ?? "" }));
|
|
16189
|
+
},
|
|
16190
|
+
close() {},
|
|
16191
|
+
async* iterateForSnapshot(table, signal) {
|
|
16192
|
+
const columns = await adapter.getColumnsAsync(table, signal);
|
|
16193
|
+
const colNames = columns.map((column) => column.name);
|
|
16194
|
+
const pkColumns = columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
16195
|
+
let offset = 0;
|
|
16196
|
+
let rowIndex = 0;
|
|
16197
|
+
for (;; ) {
|
|
16198
|
+
if (signal?.aborted)
|
|
16199
|
+
return;
|
|
16200
|
+
const result = await adapter.getTablePageAsync(table, { offset, limit: SQL_SNAPSHOT_BATCH_SIZE }, signal);
|
|
16201
|
+
if (result.rows.length === 0)
|
|
16202
|
+
return;
|
|
16203
|
+
for (const row of result.rows) {
|
|
16204
|
+
yield {
|
|
16205
|
+
keyJson: buildRowKeyJson(pkColumns, colNames, row, rowIndex),
|
|
16206
|
+
rowHash: computeRowHash(colNames, row),
|
|
16207
|
+
payloadJson: rowToPayloadJson(colNames, row)
|
|
16208
|
+
};
|
|
16209
|
+
rowIndex++;
|
|
16210
|
+
}
|
|
16211
|
+
offset += result.rows.length;
|
|
16212
|
+
if (result.rows.length < SQL_SNAPSHOT_BATCH_SIZE)
|
|
16213
|
+
return;
|
|
16214
|
+
}
|
|
16215
|
+
},
|
|
16216
|
+
async listSnapshotContainers() {
|
|
16217
|
+
const tables = await adapter.getTablesAsync();
|
|
16218
|
+
return tables.filter((table) => table.type === "table").map((table) => ({ id: table.name, label: table.name }));
|
|
16219
|
+
}
|
|
16220
|
+
};
|
|
16221
|
+
return adapter;
|
|
16222
|
+
}
|
|
16223
|
+
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;
|
|
16224
|
+
var init_d1 = __esm(() => {
|
|
16225
|
+
init_sql_snapshot();
|
|
16226
|
+
init_sql_utils();
|
|
16227
|
+
init_sql_capture();
|
|
16228
|
+
init_sqlite_introspection();
|
|
16229
|
+
D1_INTERNAL_NAME_RE = /^_cf_/i;
|
|
16230
|
+
d1RequestTimeoutMs = DEFAULT_D1_REQUEST_TIMEOUT_MS;
|
|
16231
|
+
D1HttpError = class D1HttpError extends Error {
|
|
16232
|
+
status;
|
|
16233
|
+
constructor(status, message) {
|
|
16234
|
+
super(message);
|
|
16235
|
+
this.status = status;
|
|
16236
|
+
}
|
|
16237
|
+
};
|
|
16238
|
+
});
|
|
16239
|
+
|
|
15853
16240
|
// web-src/server/database/adapters/dynamodb.ts
|
|
15854
16241
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
15855
16242
|
import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
|
|
@@ -16388,6 +16775,136 @@ var init_connection_pool = __esm(() => {
|
|
|
16388
16775
|
pool = new Map;
|
|
16389
16776
|
});
|
|
16390
16777
|
|
|
16778
|
+
// web-src/server/database/credential-store.ts
|
|
16779
|
+
function isKeychainAvailable() {
|
|
16780
|
+
if (keychainEnabledOverride !== null)
|
|
16781
|
+
return keychainEnabledOverride;
|
|
16782
|
+
if (false)
|
|
16783
|
+
;
|
|
16784
|
+
return process.platform === "darwin";
|
|
16785
|
+
}
|
|
16786
|
+
function accountFor(cwd, connectionId) {
|
|
16787
|
+
return `${cwd}#${connectionId}`;
|
|
16788
|
+
}
|
|
16789
|
+
function quoteSecurityArg(value) {
|
|
16790
|
+
if (hasControlCharacter(value))
|
|
16791
|
+
return null;
|
|
16792
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
16793
|
+
}
|
|
16794
|
+
async function runSecurityAsync(opts) {
|
|
16795
|
+
if (!keychainSpawnOverride && false) {}
|
|
16796
|
+
const spawn3 = keychainSpawnOverride ?? spawnCollectAsync;
|
|
16797
|
+
const result = await spawn3({
|
|
16798
|
+
command: SECURITY_COMMAND,
|
|
16799
|
+
args: opts.args,
|
|
16800
|
+
...opts.input === undefined ? {} : { input: opts.input },
|
|
16801
|
+
timeoutMs: KEYCHAIN_TIMEOUT_MS,
|
|
16802
|
+
abortMessage: "keychain access aborted",
|
|
16803
|
+
timeoutMessage: `keychain access timed out after ${KEYCHAIN_TIMEOUT_MS}ms`,
|
|
16804
|
+
rejectOnError: false
|
|
16805
|
+
});
|
|
16806
|
+
return {
|
|
16807
|
+
stdout: result.stdout.toString("utf8"),
|
|
16808
|
+
stderr: result.stderr.toString("utf8"),
|
|
16809
|
+
code: result.code
|
|
16810
|
+
};
|
|
16811
|
+
}
|
|
16812
|
+
function warnKeychain(action, detail) {
|
|
16813
|
+
console.warn(`[code-viewer] keychain ${action} failed: ${detail.replace(/\s+/g, " ").trim().slice(0, 200)}`);
|
|
16814
|
+
}
|
|
16815
|
+
async function saveConnectionSecretsAsync(cwd, connectionId, secrets) {
|
|
16816
|
+
if (!isKeychainAvailable())
|
|
16817
|
+
return false;
|
|
16818
|
+
if (Object.keys(secrets).length === 0) {
|
|
16819
|
+
return deleteConnectionSecretsAsync(cwd, connectionId);
|
|
16820
|
+
}
|
|
16821
|
+
const account = quoteSecurityArg(accountFor(cwd, connectionId));
|
|
16822
|
+
const label = quoteSecurityArg(`code-viewer: ${connectionId}`);
|
|
16823
|
+
if (!account || !label) {
|
|
16824
|
+
warnKeychain("save", "connection id or path contains control characters");
|
|
16825
|
+
return false;
|
|
16826
|
+
}
|
|
16827
|
+
const payload = Buffer.from(JSON.stringify(secrets), "utf8").toString("base64");
|
|
16828
|
+
try {
|
|
16829
|
+
const result = await runSecurityAsync({
|
|
16830
|
+
args: ["-i"],
|
|
16831
|
+
input: `add-generic-password -U -s "${KEYCHAIN_SERVICE}" -a ${account} -l ${label} -w "${payload}"
|
|
16832
|
+
`
|
|
16833
|
+
});
|
|
16834
|
+
if (result.code !== 0) {
|
|
16835
|
+
warnKeychain("save", result.stderr || `exit ${result.code}`);
|
|
16836
|
+
return false;
|
|
16837
|
+
}
|
|
16838
|
+
return true;
|
|
16839
|
+
} catch (err) {
|
|
16840
|
+
warnKeychain("save", err instanceof Error ? err.message : String(err));
|
|
16841
|
+
return false;
|
|
16842
|
+
}
|
|
16843
|
+
}
|
|
16844
|
+
async function loadConnectionSecretsAsync(cwd, connectionId) {
|
|
16845
|
+
if (!isKeychainAvailable())
|
|
16846
|
+
return null;
|
|
16847
|
+
const account = accountFor(cwd, connectionId);
|
|
16848
|
+
if (hasControlCharacter(account))
|
|
16849
|
+
return null;
|
|
16850
|
+
try {
|
|
16851
|
+
const result = await runSecurityAsync({
|
|
16852
|
+
args: [
|
|
16853
|
+
"find-generic-password",
|
|
16854
|
+
"-s",
|
|
16855
|
+
KEYCHAIN_SERVICE,
|
|
16856
|
+
"-a",
|
|
16857
|
+
account,
|
|
16858
|
+
"-w"
|
|
16859
|
+
]
|
|
16860
|
+
});
|
|
16861
|
+
if (result.code === ERR_SEC_ITEM_NOT_FOUND)
|
|
16862
|
+
return null;
|
|
16863
|
+
if (result.code !== 0) {
|
|
16864
|
+
warnKeychain("read", result.stderr || `exit ${result.code}`);
|
|
16865
|
+
return null;
|
|
16866
|
+
}
|
|
16867
|
+
const decoded = Buffer.from(result.stdout.trim(), "base64").toString("utf8");
|
|
16868
|
+
const parsed = JSON.parse(decoded);
|
|
16869
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
16870
|
+
return null;
|
|
16871
|
+
}
|
|
16872
|
+
const secrets = {};
|
|
16873
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
16874
|
+
if (typeof value === "string")
|
|
16875
|
+
secrets[key] = value;
|
|
16876
|
+
}
|
|
16877
|
+
return Object.keys(secrets).length > 0 ? secrets : null;
|
|
16878
|
+
} catch (err) {
|
|
16879
|
+
warnKeychain("read", err instanceof Error ? err.message : String(err));
|
|
16880
|
+
return null;
|
|
16881
|
+
}
|
|
16882
|
+
}
|
|
16883
|
+
async function deleteConnectionSecretsAsync(cwd, connectionId) {
|
|
16884
|
+
if (!isKeychainAvailable())
|
|
16885
|
+
return false;
|
|
16886
|
+
const account = accountFor(cwd, connectionId);
|
|
16887
|
+
if (hasControlCharacter(account))
|
|
16888
|
+
return false;
|
|
16889
|
+
try {
|
|
16890
|
+
const result = await runSecurityAsync({
|
|
16891
|
+
args: ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account]
|
|
16892
|
+
});
|
|
16893
|
+
if (result.code !== 0 && result.code !== ERR_SEC_ITEM_NOT_FOUND) {
|
|
16894
|
+
warnKeychain("delete", result.stderr || `exit ${result.code}`);
|
|
16895
|
+
return false;
|
|
16896
|
+
}
|
|
16897
|
+
return true;
|
|
16898
|
+
} catch (err) {
|
|
16899
|
+
warnKeychain("delete", err instanceof Error ? err.message : String(err));
|
|
16900
|
+
return false;
|
|
16901
|
+
}
|
|
16902
|
+
}
|
|
16903
|
+
var SECURITY_COMMAND = "/usr/bin/security", KEYCHAIN_SERVICE = "code-viewer", KEYCHAIN_TIMEOUT_MS = 5000, ERR_SEC_ITEM_NOT_FOUND = 44, keychainEnabledOverride = null, keychainSpawnOverride = null;
|
|
16904
|
+
var init_credential_store = __esm(() => {
|
|
16905
|
+
init_spawn_runner();
|
|
16906
|
+
});
|
|
16907
|
+
|
|
16391
16908
|
// web-src/server/database/connections-store.ts
|
|
16392
16909
|
import { randomUUID } from "node:crypto";
|
|
16393
16910
|
import { chmod } from "node:fs/promises";
|
|
@@ -16395,15 +16912,45 @@ import { join as join12 } from "node:path";
|
|
|
16395
16912
|
function secretKey(cwd, id) {
|
|
16396
16913
|
return `${cwd}\x00${id}`;
|
|
16397
16914
|
}
|
|
16915
|
+
async function hydrateFromKeychainAsync(cwd, connections) {
|
|
16916
|
+
if (!isKeychainAvailable())
|
|
16917
|
+
return;
|
|
16918
|
+
await Promise.all(connections.map((connection) => {
|
|
16919
|
+
const key = secretKey(cwd, connection.id);
|
|
16920
|
+
if (runtimeSecrets.has(key))
|
|
16921
|
+
return;
|
|
16922
|
+
const inFlight = keychainLookups.get(key);
|
|
16923
|
+
if (inFlight)
|
|
16924
|
+
return inFlight;
|
|
16925
|
+
const lookup = loadConnectionSecretsAsync(cwd, connection.id).then((stored) => {
|
|
16926
|
+
if (stored)
|
|
16927
|
+
runtimeSecrets.set(key, extractSecrets(stored));
|
|
16928
|
+
}).catch(() => {
|
|
16929
|
+
keychainLookups.delete(key);
|
|
16930
|
+
});
|
|
16931
|
+
keychainLookups.set(key, lookup);
|
|
16932
|
+
return lookup;
|
|
16933
|
+
}));
|
|
16934
|
+
}
|
|
16398
16935
|
function extractSecrets(value) {
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
|
|
16405
|
-
|
|
16406
|
-
|
|
16936
|
+
const secrets = {};
|
|
16937
|
+
for (const field of RUNTIME_ONLY_FIELDS) {
|
|
16938
|
+
if (typeof value[field] === "string")
|
|
16939
|
+
secrets[field] = value[field];
|
|
16940
|
+
}
|
|
16941
|
+
return secrets;
|
|
16942
|
+
}
|
|
16943
|
+
function preservedSecrets(input, existing) {
|
|
16944
|
+
if (!existing)
|
|
16945
|
+
return {};
|
|
16946
|
+
const preserved = {};
|
|
16947
|
+
const source = existing;
|
|
16948
|
+
for (const field of PRESERVED_SECRET_FIELDS) {
|
|
16949
|
+
if (input[field] === undefined && field in source) {
|
|
16950
|
+
preserved[field] = source[field];
|
|
16951
|
+
}
|
|
16952
|
+
}
|
|
16953
|
+
return preserved;
|
|
16407
16954
|
}
|
|
16408
16955
|
function withRuntimeSecrets(cwd, connection) {
|
|
16409
16956
|
return {
|
|
@@ -16497,6 +17044,19 @@ function sanitizeConnection(raw) {
|
|
|
16497
17044
|
password: requiredString(input.password)
|
|
16498
17045
|
};
|
|
16499
17046
|
}
|
|
17047
|
+
if (input.kind === "d1") {
|
|
17048
|
+
const accountId = requiredString(input.accountId, 128).trim();
|
|
17049
|
+
const databaseId = requiredString(input.databaseId, 128).trim();
|
|
17050
|
+
if (!accountId || !databaseId)
|
|
17051
|
+
return null;
|
|
17052
|
+
return {
|
|
17053
|
+
...base,
|
|
17054
|
+
kind: "d1",
|
|
17055
|
+
accountId,
|
|
17056
|
+
databaseId,
|
|
17057
|
+
apiToken: requiredString(input.apiToken)
|
|
17058
|
+
};
|
|
17059
|
+
}
|
|
16500
17060
|
if (input.kind === "s3" || input.kind === "dynamodb") {
|
|
16501
17061
|
const endpoint = validEndpoint(input.endpoint);
|
|
16502
17062
|
const region = requiredString(input.region).trim();
|
|
@@ -16515,16 +17075,19 @@ function sanitizeConnection(raw) {
|
|
|
16515
17075
|
}
|
|
16516
17076
|
return null;
|
|
16517
17077
|
}
|
|
17078
|
+
function assertConnectionCredentials(connection) {
|
|
17079
|
+
const missing = !connection || (connection.kind === "postgresql" || connection.kind === "mysql") && !connection.user || (connection.kind === "s3" || connection.kind === "dynamodb") && !connection.accessKeyId || connection.kind === "d1" && !connection.apiToken;
|
|
17080
|
+
if (!connection || missing) {
|
|
17081
|
+
throw new Error("invalid datastore connection");
|
|
17082
|
+
}
|
|
17083
|
+
return connection;
|
|
17084
|
+
}
|
|
16518
17085
|
function validateDatastoreConnection(raw, fallbackId = "connection:0000000000000000") {
|
|
16519
17086
|
const input = raw && typeof raw === "object" ? raw : {};
|
|
16520
|
-
|
|
17087
|
+
return assertConnectionCredentials(sanitizeConnection({
|
|
16521
17088
|
...input,
|
|
16522
17089
|
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;
|
|
17090
|
+
}));
|
|
16528
17091
|
}
|
|
16529
17092
|
function sanitizeState(raw) {
|
|
16530
17093
|
if (!raw || typeof raw !== "object")
|
|
@@ -16552,7 +17115,9 @@ async function protectFile(cwd) {
|
|
|
16552
17115
|
});
|
|
16553
17116
|
}
|
|
16554
17117
|
async function loadDatastoreConnections(cwd) {
|
|
16555
|
-
|
|
17118
|
+
const { connections } = await store.load(cwd);
|
|
17119
|
+
await hydrateFromKeychainAsync(cwd, connections);
|
|
17120
|
+
return connections.map((connection) => withRuntimeSecrets(cwd, connection));
|
|
16556
17121
|
}
|
|
16557
17122
|
async function findDatastoreConnection(cwd, id) {
|
|
16558
17123
|
return (await loadDatastoreConnections(cwd)).find((entry) => entry.id === id) ?? null;
|
|
@@ -16564,17 +17129,12 @@ async function saveDatastoreConnection(cwd, raw) {
|
|
|
16564
17129
|
const result = await store.update(cwd, (state) => {
|
|
16565
17130
|
const storedExisting = state.connections.find((entry) => entry.id === id);
|
|
16566
17131
|
const existing = storedExisting ? withRuntimeSecrets(cwd, storedExisting) : undefined;
|
|
16567
|
-
const merged = sanitizeConnection({
|
|
17132
|
+
const merged = assertConnectionCredentials(sanitizeConnection({
|
|
16568
17133
|
...existing ?? {},
|
|
16569
17134
|
...input,
|
|
16570
17135
|
id,
|
|
16571
|
-
|
|
16572
|
-
|
|
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
|
-
}
|
|
17136
|
+
...preservedSecrets(input, existing)
|
|
17137
|
+
}));
|
|
16578
17138
|
const connections = state.connections.filter((entry) => entry.id !== id);
|
|
16579
17139
|
if (!storedExisting && connections.length >= MAX_CONNECTIONS2) {
|
|
16580
17140
|
throw new Error("too many datastore connections");
|
|
@@ -16585,7 +17145,10 @@ async function saveDatastoreConnection(cwd, raw) {
|
|
|
16585
17145
|
result: merged
|
|
16586
17146
|
};
|
|
16587
17147
|
});
|
|
16588
|
-
|
|
17148
|
+
const secrets = extractSecrets(result);
|
|
17149
|
+
runtimeSecrets.set(secretKey(cwd, result.id), secrets);
|
|
17150
|
+
keychainLookups.set(secretKey(cwd, result.id), Promise.resolve());
|
|
17151
|
+
await saveConnectionSecretsAsync(cwd, result.id, secrets);
|
|
16589
17152
|
await protectFile(cwd);
|
|
16590
17153
|
return result;
|
|
16591
17154
|
}
|
|
@@ -16598,8 +17161,10 @@ async function deleteDatastoreConnection(cwd, id) {
|
|
|
16598
17161
|
};
|
|
16599
17162
|
});
|
|
16600
17163
|
runtimeSecrets.delete(secretKey(cwd, id));
|
|
17164
|
+
keychainLookups.delete(secretKey(cwd, id));
|
|
17165
|
+
const secretsRemoved = isKeychainAvailable() ? await deleteConnectionSecretsAsync(cwd, id) : true;
|
|
16601
17166
|
await protectFile(cwd);
|
|
16602
|
-
return deleted;
|
|
17167
|
+
return { deleted, secretsRemoved };
|
|
16603
17168
|
}
|
|
16604
17169
|
function connectionToFileInfo(connection) {
|
|
16605
17170
|
return {
|
|
@@ -16612,25 +17177,33 @@ function connectionToFileInfo(connection) {
|
|
|
16612
17177
|
};
|
|
16613
17178
|
}
|
|
16614
17179
|
function publicConnection(connection) {
|
|
16615
|
-
const {
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
username: _username,
|
|
16619
|
-
accessKeyId: _accessKeyId,
|
|
16620
|
-
...withoutPassword
|
|
16621
|
-
} = connection;
|
|
16622
|
-
const {
|
|
16623
|
-
secretAccessKey: _secret,
|
|
16624
|
-
sessionToken: _token,
|
|
16625
|
-
...safe
|
|
16626
|
-
} = withoutPassword;
|
|
17180
|
+
const safe = { ...connection };
|
|
17181
|
+
for (const field of RUNTIME_ONLY_FIELDS)
|
|
17182
|
+
delete safe[field];
|
|
16627
17183
|
return safe;
|
|
16628
17184
|
}
|
|
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;
|
|
17185
|
+
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
17186
|
var init_connections_store = __esm(() => {
|
|
16631
17187
|
init_json_store();
|
|
17188
|
+
init_credential_store();
|
|
16632
17189
|
MAX_JSON_BYTES = 256 * 1024;
|
|
17190
|
+
RUNTIME_ONLY_FIELDS = [
|
|
17191
|
+
"user",
|
|
17192
|
+
"username",
|
|
17193
|
+
"accessKeyId",
|
|
17194
|
+
"password",
|
|
17195
|
+
"secretAccessKey",
|
|
17196
|
+
"sessionToken",
|
|
17197
|
+
"apiToken"
|
|
17198
|
+
];
|
|
17199
|
+
PRESERVED_SECRET_FIELDS = [
|
|
17200
|
+
"password",
|
|
17201
|
+
"secretAccessKey",
|
|
17202
|
+
"sessionToken",
|
|
17203
|
+
"apiToken"
|
|
17204
|
+
];
|
|
16633
17205
|
runtimeSecrets = new Map;
|
|
17206
|
+
keychainLookups = new Map;
|
|
16634
17207
|
store = createJsonFileStore({
|
|
16635
17208
|
filePath: connectionsFilePath,
|
|
16636
17209
|
empty: emptyState,
|
|
@@ -16655,7 +17228,7 @@ function escapeLikeTerm(term) {
|
|
|
16655
17228
|
return term.replace(/=/g, "==").replace(/%/g, "=%").replace(/_/g, "=_");
|
|
16656
17229
|
}
|
|
16657
17230
|
async function searchTableAsync(adapter, table, columns, term, maxHits, includeNonText, pkColumns, signal) {
|
|
16658
|
-
const kind = adapter.kind;
|
|
17231
|
+
const kind = adapter.kind === "d1" ? "sqlite" : adapter.kind;
|
|
16659
17232
|
const searchCols = includeNonText ? columns.filter((c) => c.type.toUpperCase() !== "BLOB" && c.type.toUpperCase() !== "BYTEA") : columns.filter((c) => isTextLikeType(c.type));
|
|
16660
17233
|
if (searchCols.length === 0)
|
|
16661
17234
|
return [];
|
|
@@ -17024,6 +17597,9 @@ function handleError(prefix, action, err, signal) {
|
|
|
17024
17597
|
if (isDockerComposeServiceUnavailableError(err)) {
|
|
17025
17598
|
return textError(message, err.status);
|
|
17026
17599
|
}
|
|
17600
|
+
if (isD1HttpError(err)) {
|
|
17601
|
+
return textError(message, err.status);
|
|
17602
|
+
}
|
|
17027
17603
|
if (isFilesystemAccessError(err)) {
|
|
17028
17604
|
return textError(`failed to ${action}`, 500);
|
|
17029
17605
|
}
|
|
@@ -17031,6 +17607,7 @@ function handleError(prefix, action, err, signal) {
|
|
|
17031
17607
|
}
|
|
17032
17608
|
var DEFAULT_MAX_DOCKER_ADAPTER_CACHE = 8, DEFAULT_DOCKER_ADAPTER_IDLE_MS, MAX_LOGGED_ERROR_BODY = 500, logQueue;
|
|
17033
17609
|
var init_handle_shared = __esm(() => {
|
|
17610
|
+
init_d1();
|
|
17034
17611
|
init_docker_utils();
|
|
17035
17612
|
init_connections_store();
|
|
17036
17613
|
init_discovery();
|
|
@@ -19202,6 +19779,10 @@ function ensureInit() {
|
|
|
19202
19779
|
initialized = true;
|
|
19203
19780
|
}
|
|
19204
19781
|
async function getAdapter(r, _cwd, signal) {
|
|
19782
|
+
if (r.d1) {
|
|
19783
|
+
const connection = r.d1;
|
|
19784
|
+
return dockerAdapterCache.getOrOpenAsync(r.dbId, () => createD1Adapter(connection));
|
|
19785
|
+
}
|
|
19205
19786
|
if (r.saved) {
|
|
19206
19787
|
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
19207
19788
|
return dockerAdapterCache.getOrOpenAsync(cacheKey, () => createSqlCliAdapter({ ...r.saved, schema: r.schema }));
|
|
@@ -19257,6 +19838,9 @@ async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
|
|
|
19257
19838
|
const connection = await findDatastoreConnection(cwd, dbParam);
|
|
19258
19839
|
if (!connection)
|
|
19259
19840
|
return textError("datastore connection not found", 404);
|
|
19841
|
+
if (connection.kind === "d1") {
|
|
19842
|
+
return { resolved: dbParam, dbId: dbParam, d1: connection };
|
|
19843
|
+
}
|
|
19260
19844
|
if (connection.kind !== "postgresql" && connection.kind !== "mysql") {
|
|
19261
19845
|
return textError(`${connection.kind} must use its datastore routes`, 400);
|
|
19262
19846
|
}
|
|
@@ -20466,7 +21050,7 @@ async function handleDbUiGet(cwd) {
|
|
|
20466
21050
|
return jsonLoadResponse(() => loadDbUiState(cwd), "db UI", "failed to load db UI state");
|
|
20467
21051
|
}
|
|
20468
21052
|
function closeSavedConnection(id, kind) {
|
|
20469
|
-
if (kind === "postgresql" || kind === "mysql") {
|
|
21053
|
+
if (kind === "postgresql" || kind === "mysql" || kind === "d1") {
|
|
20470
21054
|
dockerAdapterCache.close(id);
|
|
20471
21055
|
dockerAdapterCache.closePrefix(`${id}\x00`);
|
|
20472
21056
|
return;
|
|
@@ -20498,9 +21082,9 @@ async function handleConnections(cwd, req) {
|
|
|
20498
21082
|
const existing = await findDatastoreConnection(cwd, id);
|
|
20499
21083
|
if (!existing)
|
|
20500
21084
|
return textError("datastore connection not found", 404);
|
|
20501
|
-
await deleteDatastoreConnection(cwd, id);
|
|
21085
|
+
const { secretsRemoved } = await deleteDatastoreConnection(cwd, id);
|
|
20502
21086
|
closeSavedConnection(id, existing.kind);
|
|
20503
|
-
return json({ ok: true });
|
|
21087
|
+
return json({ ok: true, secretsRemoved });
|
|
20504
21088
|
}
|
|
20505
21089
|
async function probeDatastoreConnection(connection, signal) {
|
|
20506
21090
|
if (connection.kind === "postgresql" || connection.kind === "mysql") {
|
|
@@ -20512,6 +21096,15 @@ async function probeDatastoreConnection(connection, signal) {
|
|
|
20512
21096
|
}
|
|
20513
21097
|
return;
|
|
20514
21098
|
}
|
|
21099
|
+
if (connection.kind === "d1") {
|
|
21100
|
+
const adapter2 = createD1Adapter(connection);
|
|
21101
|
+
try {
|
|
21102
|
+
await adapter2.getTablesAsync(signal);
|
|
21103
|
+
} finally {
|
|
21104
|
+
adapter2.close();
|
|
21105
|
+
}
|
|
21106
|
+
return;
|
|
21107
|
+
}
|
|
20515
21108
|
if (connection.kind === "redis") {
|
|
20516
21109
|
const adapter2 = createRedisAdapter(connection);
|
|
20517
21110
|
try {
|
|
@@ -20831,6 +21424,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
20831
21424
|
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
21425
|
var init_handle = __esm(() => {
|
|
20833
21426
|
init_state_store();
|
|
21427
|
+
init_d1();
|
|
20834
21428
|
init_docker();
|
|
20835
21429
|
init_docker_utils();
|
|
20836
21430
|
init_dynamodb();
|
|
@@ -20900,7 +21494,7 @@ var init_handle = __esm(() => {
|
|
|
20900
21494
|
});
|
|
20901
21495
|
|
|
20902
21496
|
// web-src/server/doctor.ts
|
|
20903
|
-
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as
|
|
21497
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync6 } from "node:fs";
|
|
20904
21498
|
import { dirname as dirname5, join as join16, relative as relative6 } from "node:path";
|
|
20905
21499
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20906
21500
|
function statusWorse(a, b) {
|
|
@@ -21093,7 +21687,7 @@ async function checkSqlite(cwd) {
|
|
|
21093
21687
|
async function trySnapshotDbOpen(cwd) {
|
|
21094
21688
|
const dbPath = join16(cwd, SNAPSHOT_DB_REL);
|
|
21095
21689
|
try {
|
|
21096
|
-
|
|
21690
|
+
statSync6(dbPath);
|
|
21097
21691
|
} catch {
|
|
21098
21692
|
return { kind: "skipped" };
|
|
21099
21693
|
}
|
|
@@ -21122,7 +21716,7 @@ function checkSnapshotStore(cwd) {
|
|
|
21122
21716
|
dirDetail = `${dir} (writable)`;
|
|
21123
21717
|
} catch {
|
|
21124
21718
|
try {
|
|
21125
|
-
|
|
21719
|
+
statSync6(dir);
|
|
21126
21720
|
dirStatus = "error";
|
|
21127
21721
|
dirDetail = `${dir} (not writable)`;
|
|
21128
21722
|
dirHint = "Snapshot creation will fail until the directory is writable. " + "Check filesystem permissions on the .code-viewer directory.";
|
|
@@ -21132,7 +21726,7 @@ function checkSnapshotStore(cwd) {
|
|
|
21132
21726
|
}
|
|
21133
21727
|
let dbDetail = dbPath;
|
|
21134
21728
|
try {
|
|
21135
|
-
const stat3 =
|
|
21729
|
+
const stat3 = statSync6(dbPath);
|
|
21136
21730
|
dbDetail = `${dbPath} (${stat3.size.toLocaleString()} bytes)`;
|
|
21137
21731
|
} catch {
|
|
21138
21732
|
dbDetail = `${dbPath} (not created yet — created on first snapshot)`;
|
|
@@ -24536,7 +25130,7 @@ import {
|
|
|
24536
25130
|
readFileSync as readFileSync8,
|
|
24537
25131
|
realpathSync as realpathSync7,
|
|
24538
25132
|
renameSync,
|
|
24539
|
-
statSync as
|
|
25133
|
+
statSync as statSync7,
|
|
24540
25134
|
unlinkSync as unlinkSync2,
|
|
24541
25135
|
watch,
|
|
24542
25136
|
writeFileSync as writeFileSync2
|
|
@@ -25056,7 +25650,7 @@ function worktreeFileMetadata(path, knownSize) {
|
|
|
25056
25650
|
if (!full)
|
|
25057
25651
|
return {};
|
|
25058
25652
|
try {
|
|
25059
|
-
const stat3 =
|
|
25653
|
+
const stat3 = statSync7(full);
|
|
25060
25654
|
return {
|
|
25061
25655
|
size: knownSize ?? stat3.size,
|
|
25062
25656
|
created_at: isoDate(stat3.birthtimeMs),
|
|
@@ -25081,7 +25675,7 @@ async function directoryMetadata(target, path) {
|
|
|
25081
25675
|
if (!full)
|
|
25082
25676
|
return {};
|
|
25083
25677
|
try {
|
|
25084
|
-
const stat3 =
|
|
25678
|
+
const stat3 = statSync7(full);
|
|
25085
25679
|
return {
|
|
25086
25680
|
created_at: isoDate(stat3.birthtimeMs),
|
|
25087
25681
|
updated_at: isoDate(stat3.mtimeMs)
|
|
@@ -25182,9 +25776,14 @@ async function handleTree(url) {
|
|
|
25182
25776
|
if (tree.error)
|
|
25183
25777
|
return text(tree.error, tree.status ?? 500);
|
|
25184
25778
|
const entries = tree.entries.filter((entry) => !isExcludedScopePath(entry.path, excludeNames));
|
|
25185
|
-
const
|
|
25779
|
+
const worktreeTarget = target === "worktree" || target === "";
|
|
25780
|
+
const [statusMap, ignoredPaths] = worktreeTarget ? await Promise.all([
|
|
25781
|
+
repoStatusMapAsync(cwd),
|
|
25782
|
+
ignoredPathsAsync(entries.map((entry) => entry.path), cwd)
|
|
25783
|
+
]) : [null, null];
|
|
25186
25784
|
const withStatus = (entry) => {
|
|
25187
|
-
const
|
|
25785
|
+
const found = statusMap && repoStatusForPath(statusMap, entry.path);
|
|
25786
|
+
const status = ignoredPaths?.has(entry.path) && (!found || found.inherited) ? "I" : found?.code;
|
|
25188
25787
|
return status ? { ...entry, status } : entry;
|
|
25189
25788
|
};
|
|
25190
25789
|
const deletedEntries = !recursive && statusMap ? deletedTreeEntriesForPath(statusMap, path) : [];
|
|
@@ -25198,7 +25797,7 @@ async function handleTree(url) {
|
|
|
25198
25797
|
...deletedEntries
|
|
25199
25798
|
],
|
|
25200
25799
|
readme: await readReadme(target, path),
|
|
25201
|
-
upload_enabled: uploadEnabled &&
|
|
25800
|
+
upload_enabled: uploadEnabled && worktreeTarget
|
|
25202
25801
|
});
|
|
25203
25802
|
}
|
|
25204
25803
|
async function handleSettings() {
|
|
@@ -25358,7 +25957,7 @@ async function handleLog(url) {
|
|
|
25358
25957
|
}
|
|
25359
25958
|
function blamePathKey(p) {
|
|
25360
25959
|
try {
|
|
25361
|
-
const st =
|
|
25960
|
+
const st = statSync7(join20(cwd, p));
|
|
25362
25961
|
return `${st.mtimeMs}:${st.size}`;
|
|
25363
25962
|
} catch {
|
|
25364
25963
|
return "missing";
|
|
@@ -25513,7 +26112,7 @@ async function handleFileDiff(url) {
|
|
|
25513
26112
|
}
|
|
25514
26113
|
function worktreeLineIndexSignature(full) {
|
|
25515
26114
|
try {
|
|
25516
|
-
const stat3 =
|
|
26115
|
+
const stat3 = statSync7(full);
|
|
25517
26116
|
return `size:${stat3.size}|mtime:${stat3.mtimeMs}|ctime:${stat3.ctimeMs}|ino:${stat3.ino || 0}`;
|
|
25518
26117
|
} catch {
|
|
25519
26118
|
return null;
|
|
@@ -25529,7 +26128,7 @@ async function getWorktreeLineIndex(full) {
|
|
|
25529
26128
|
lineIndexCache.set(full, cached);
|
|
25530
26129
|
return cached.index;
|
|
25531
26130
|
}
|
|
25532
|
-
const stat3 =
|
|
26131
|
+
const stat3 = statSync7(full);
|
|
25533
26132
|
if (stat3.size > LINE_INDEX_MAX_FILE_BYTES)
|
|
25534
26133
|
return null;
|
|
25535
26134
|
const index = await buildLineOffsetIndexFromStream(fileReadableStream(full), stat3.size);
|
|
@@ -25661,6 +26260,8 @@ async function handleFileRange(url) {
|
|
|
25661
26260
|
const full = safeWorktreePath2(path);
|
|
25662
26261
|
if (!full)
|
|
25663
26262
|
return text("no file", 404);
|
|
26263
|
+
if (await rawFileSize(path, ref) == null)
|
|
26264
|
+
return text("no file", 404);
|
|
25664
26265
|
const responseGeneration = generation;
|
|
25665
26266
|
const result = await collectIndexedWorktreeLineRange(full, start, end);
|
|
25666
26267
|
const body = {
|
|
@@ -25809,7 +26410,8 @@ async function rawFileSize(path, ref) {
|
|
|
25809
26410
|
if (!full)
|
|
25810
26411
|
return null;
|
|
25811
26412
|
try {
|
|
25812
|
-
|
|
26413
|
+
const stats = statSync7(full);
|
|
26414
|
+
return stats.isFile() ? stats.size : null;
|
|
25813
26415
|
} catch {
|
|
25814
26416
|
return null;
|
|
25815
26417
|
}
|
|
@@ -25870,7 +26472,7 @@ async function handleUploadFiles(req) {
|
|
|
25870
26472
|
const realDir = safeOpenWorktreePath(dir);
|
|
25871
26473
|
if (!realDir)
|
|
25872
26474
|
return text("not found", 404);
|
|
25873
|
-
const stats =
|
|
26475
|
+
const stats = statSync7(realDir);
|
|
25874
26476
|
if (!stats.isDirectory())
|
|
25875
26477
|
return text("not a directory", 400);
|
|
25876
26478
|
const files = form.getAll("files").filter((item) => item instanceof File);
|
|
@@ -26119,7 +26721,7 @@ async function handleOpenPath(req) {
|
|
|
26119
26721
|
const target = safeOpenWorktreePath(targetPath);
|
|
26120
26722
|
if (!target)
|
|
26121
26723
|
return text("not found", 404);
|
|
26122
|
-
const stats =
|
|
26724
|
+
const stats = statSync7(target);
|
|
26123
26725
|
if (!stats.isDirectory())
|
|
26124
26726
|
return text("not a directory", 400);
|
|
26125
26727
|
openOsPath(target);
|
|
@@ -26157,7 +26759,7 @@ async function handleTrashPath(req) {
|
|
|
26157
26759
|
return text("not found", 404);
|
|
26158
26760
|
let changedPaths;
|
|
26159
26761
|
try {
|
|
26160
|
-
const stats =
|
|
26762
|
+
const stats = statSync7(originalFullPath);
|
|
26161
26763
|
if (!stats.isDirectory())
|
|
26162
26764
|
changedPaths = [path];
|
|
26163
26765
|
} catch {}
|
|
@@ -26210,7 +26812,7 @@ async function handleCreateDirectory(req) {
|
|
|
26210
26812
|
const parent = safeOpenWorktreePath(dir);
|
|
26211
26813
|
if (!parent)
|
|
26212
26814
|
return text("not found", 404);
|
|
26213
|
-
const stats =
|
|
26815
|
+
const stats = statSync7(parent);
|
|
26214
26816
|
if (!stats.isDirectory())
|
|
26215
26817
|
return text("not a directory", 400);
|
|
26216
26818
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
@@ -26260,7 +26862,7 @@ async function handleRestoreTrash(req) {
|
|
|
26260
26862
|
return text(restored.error || "undo failed", 409);
|
|
26261
26863
|
let changedPaths;
|
|
26262
26864
|
try {
|
|
26263
|
-
const stats =
|
|
26865
|
+
const stats = statSync7(worktreePath(originalPath));
|
|
26264
26866
|
if (!stats.isDirectory())
|
|
26265
26867
|
changedPaths = [originalPath];
|
|
26266
26868
|
} catch {}
|
|
@@ -27026,6 +27628,12 @@ data: ${watchLimitReached}
|
|
|
27026
27628
|
root: cwd,
|
|
27027
27629
|
started_at: new Date().toISOString()
|
|
27028
27630
|
});
|
|
27631
|
+
process.on("uncaughtException", (error) => {
|
|
27632
|
+
console.error("[code-viewer] uncaught exception (server kept running):", error);
|
|
27633
|
+
});
|
|
27634
|
+
process.on("unhandledRejection", (reason) => {
|
|
27635
|
+
console.error("[code-viewer] unhandled rejection (server kept running):", reason);
|
|
27636
|
+
});
|
|
27029
27637
|
process.on("exit", () => {
|
|
27030
27638
|
removeServerRegistry(cwd, process.pid);
|
|
27031
27639
|
closeSseClients();
|