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