@youtyan/code-viewer 0.8.0 → 0.8.2
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 +9 -4
- package/dist/code-viewer.js +138 -29
- package/package.json +1 -1
- package/web/app.js +346 -133
- package/web/style.css +46 -24
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ Requires Node.js 20 or newer when installed from npm. Development uses
|
|
|
37
37
|
viewer.
|
|
38
38
|
Local Supabase CLI (`supabase start`) Postgres projects are auto-discovered
|
|
39
39
|
too, without needing a `docker-compose.yml`.
|
|
40
|
+
Table descriptions appear inside expanded table entries and in the Schema
|
|
41
|
+
tab header when the database provides them.
|
|
40
42
|
- Read the built-in Help page for getting started, the `.code-viewer/`
|
|
41
43
|
project files, AI annotations, datastores, the agent skill, and
|
|
42
44
|
keybindings.
|
|
@@ -176,7 +178,9 @@ files or directory names completely.
|
|
|
176
178
|
|
|
177
179
|
Scope settings control directory exclusions shared by the sidebar, Ctrl+K file
|
|
178
180
|
palette, Ctrl+G grep palette, the Datastores browser, and the file change
|
|
179
|
-
watcher — the same list applies to all five.
|
|
181
|
+
watcher — the same list applies to all five. Both the skip list and the hide
|
|
182
|
+
list accept gitignore-style wildcards (`*`, `?`, `[abc]`, `[!abc]`) in
|
|
183
|
+
addition to exact names. Everything you change in Viewer
|
|
180
184
|
Settings is saved on the server under `.code-viewer/settings.json` (no
|
|
181
185
|
separate project-level config file). `.DS_Store` and a broad set of
|
|
182
186
|
build/cache directories (`node_modules`, `dist`, `build`, `.next`, `.turbo`,
|
|
@@ -247,9 +251,10 @@ concurrency via `_seq_no` / `_primary_term`), create new documents, and delete
|
|
|
247
251
|
existing ones. Snapshots and diffs over `_search` iteration are supported.
|
|
248
252
|
|
|
249
253
|
**DynamoDB** support: detect LocalStack services with DynamoDB enabled, list
|
|
250
|
-
tables across paginated responses,
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
tables across paginated responses, and browse a Structure tab (key schema,
|
|
255
|
+
global/local secondary indexes, and non-key attribute types inferred from
|
|
256
|
+
loaded items) alongside scan or query items, `LastEvaluatedKey` pagination,
|
|
257
|
+
and an item detail view with a copyable key. The explorer is read-only.
|
|
253
258
|
|
|
254
259
|
**S3-compatible object storage** (MinIO, LocalStack): browse
|
|
255
260
|
buckets as a folder tree, search by prefix or filename, sort scanned objects by
|
package/dist/code-viewer.js
CHANGED
|
@@ -835,6 +835,110 @@ var init_command_resolver = __esm(() => {
|
|
|
835
835
|
activeOverrides = new Map;
|
|
836
836
|
});
|
|
837
837
|
|
|
838
|
+
// web-src/server/name-pattern.ts
|
|
839
|
+
function parseGlobSegment(pattern) {
|
|
840
|
+
const matchers = [];
|
|
841
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
842
|
+
const ch = pattern[i];
|
|
843
|
+
if (ch === "*") {
|
|
844
|
+
matchers.push({ kind: "star" });
|
|
845
|
+
} else if (ch === "?") {
|
|
846
|
+
matchers.push({ kind: "any" });
|
|
847
|
+
} else if (ch === "[") {
|
|
848
|
+
const close = pattern.indexOf("]", i + 1);
|
|
849
|
+
if (close === -1) {
|
|
850
|
+
matchers.push({ kind: "literal", ch: "[" });
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
const rawBody = pattern.slice(i + 1, close);
|
|
854
|
+
const negate = rawBody.startsWith("!");
|
|
855
|
+
matchers.push({
|
|
856
|
+
kind: "class",
|
|
857
|
+
body: negate ? rawBody.slice(1) : rawBody,
|
|
858
|
+
negate
|
|
859
|
+
});
|
|
860
|
+
i = close;
|
|
861
|
+
} else {
|
|
862
|
+
matchers.push({ kind: "literal", ch });
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return matchers;
|
|
866
|
+
}
|
|
867
|
+
function charInClassBody(ch, body) {
|
|
868
|
+
for (let i = 0;i < body.length; ) {
|
|
869
|
+
if (body[i + 1] === "-" && i + 2 < body.length) {
|
|
870
|
+
if (ch >= body[i] && ch <= body[i + 2])
|
|
871
|
+
return true;
|
|
872
|
+
i += 3;
|
|
873
|
+
} else {
|
|
874
|
+
if (ch === body[i])
|
|
875
|
+
return true;
|
|
876
|
+
i += 1;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
function matchesAt(matcher, ch) {
|
|
882
|
+
switch (matcher.kind) {
|
|
883
|
+
case "literal":
|
|
884
|
+
return matcher.ch === ch;
|
|
885
|
+
case "any":
|
|
886
|
+
return true;
|
|
887
|
+
case "class":
|
|
888
|
+
return charInClassBody(ch, matcher.body) !== matcher.negate;
|
|
889
|
+
case "star":
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function matchGlobSegment(matchers, name) {
|
|
894
|
+
let mi = 0;
|
|
895
|
+
let ni = 0;
|
|
896
|
+
let starMi = -1;
|
|
897
|
+
let starNi = -1;
|
|
898
|
+
while (ni < name.length) {
|
|
899
|
+
const matcher = matchers[mi];
|
|
900
|
+
if (matcher && matcher.kind === "star") {
|
|
901
|
+
starMi = mi;
|
|
902
|
+
starNi = ni;
|
|
903
|
+
mi++;
|
|
904
|
+
} else if (matcher && matchesAt(matcher, name[ni])) {
|
|
905
|
+
mi++;
|
|
906
|
+
ni++;
|
|
907
|
+
} else if (starMi !== -1) {
|
|
908
|
+
mi = starMi + 1;
|
|
909
|
+
starNi++;
|
|
910
|
+
ni = starNi;
|
|
911
|
+
} else {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
while (matchers[mi]?.kind === "star")
|
|
916
|
+
mi++;
|
|
917
|
+
return mi === matchers.length;
|
|
918
|
+
}
|
|
919
|
+
function compileNamePatterns(patterns) {
|
|
920
|
+
const literals = new Set;
|
|
921
|
+
const globs = [];
|
|
922
|
+
for (const pattern of patterns) {
|
|
923
|
+
const lower = pattern.toLowerCase();
|
|
924
|
+
if (GLOB_CHARS.test(lower)) {
|
|
925
|
+
globs.push(parseGlobSegment(lower));
|
|
926
|
+
} else {
|
|
927
|
+
literals.add(lower);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
matches(name) {
|
|
932
|
+
const lower = name.toLowerCase();
|
|
933
|
+
return literals.has(lower) || globs.some((matchers) => matchGlobSegment(matchers, lower));
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
var GLOB_CHARS;
|
|
938
|
+
var init_name_pattern = __esm(() => {
|
|
939
|
+
GLOB_CHARS = /[*?[\]]/;
|
|
940
|
+
});
|
|
941
|
+
|
|
838
942
|
// web-src/server/runtime.ts
|
|
839
943
|
import { spawn, spawnSync } from "node:child_process";
|
|
840
944
|
import { createReadStream, promises as fs } from "node:fs";
|
|
@@ -2110,7 +2214,7 @@ function sortTreeEntries(entries) {
|
|
|
2110
2214
|
function omittedWorktreeDirectoryReason(name, omitDirNames) {
|
|
2111
2215
|
if (name === ".git")
|
|
2112
2216
|
return "internal";
|
|
2113
|
-
return omitDirNames.
|
|
2217
|
+
return omitDirNames.matches(name) ? "heavy" : undefined;
|
|
2114
2218
|
}
|
|
2115
2219
|
function worktreeSubmodulePaths(cwd) {
|
|
2116
2220
|
if (!existsSync(join3(cwd, ".gitmodules")))
|
|
@@ -2137,7 +2241,7 @@ async function worktreeSubmodulePathsAsync(cwd) {
|
|
|
2137
2241
|
}).filter(Boolean));
|
|
2138
2242
|
}
|
|
2139
2243
|
function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames, submodulePaths) {
|
|
2140
|
-
if (excludeNames.
|
|
2244
|
+
if (excludeNames.matches(name))
|
|
2141
2245
|
return {
|
|
2142
2246
|
name,
|
|
2143
2247
|
path: "",
|
|
@@ -2157,8 +2261,8 @@ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, exc
|
|
|
2157
2261
|
function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
|
|
2158
2262
|
const base = normalizeTreePath(path);
|
|
2159
2263
|
const root = join3(cwd, base);
|
|
2160
|
-
const omitDirNameSet =
|
|
2161
|
-
const excludeNameSet =
|
|
2264
|
+
const omitDirNameSet = compileNamePatterns(omitDirNames);
|
|
2265
|
+
const excludeNameSet = compileNamePatterns(excludeNames);
|
|
2162
2266
|
const submodulePaths = worktreeSubmodulePaths(cwd);
|
|
2163
2267
|
let directEntries;
|
|
2164
2268
|
try {
|
|
@@ -2200,7 +2304,7 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
2200
2304
|
return;
|
|
2201
2305
|
}
|
|
2202
2306
|
for (const entry of entries) {
|
|
2203
|
-
if (excludeNameSet.
|
|
2307
|
+
if (excludeNameSet.matches(entry.name))
|
|
2204
2308
|
continue;
|
|
2205
2309
|
const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2206
2310
|
const full = join3(dir, entry.name);
|
|
@@ -2236,8 +2340,8 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
2236
2340
|
async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
|
|
2237
2341
|
const base = normalizeTreePath(path);
|
|
2238
2342
|
const root = join3(cwd, base);
|
|
2239
|
-
const omitDirNameSet =
|
|
2240
|
-
const excludeNameSet =
|
|
2343
|
+
const omitDirNameSet = compileNamePatterns(omitDirNames);
|
|
2344
|
+
const excludeNameSet = compileNamePatterns(excludeNames);
|
|
2241
2345
|
const submodulePaths = await worktreeSubmodulePathsAsync(cwd);
|
|
2242
2346
|
let directEntries;
|
|
2243
2347
|
try {
|
|
@@ -2287,7 +2391,7 @@ async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames
|
|
|
2287
2391
|
return;
|
|
2288
2392
|
}
|
|
2289
2393
|
for (const entry of entries) {
|
|
2290
|
-
if (excludeNameSet.
|
|
2394
|
+
if (excludeNameSet.matches(entry.name))
|
|
2291
2395
|
continue;
|
|
2292
2396
|
const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2293
2397
|
const full = join3(dir, entry.name);
|
|
@@ -2726,6 +2830,7 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
2726
2830
|
var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
2727
2831
|
var init_git = __esm(() => {
|
|
2728
2832
|
init_command_resolver();
|
|
2833
|
+
init_name_pattern();
|
|
2729
2834
|
init_runtime();
|
|
2730
2835
|
ALWAYS_WORKTREE_OMIT_DIR_NAMES = [".devbox", ".direnv"];
|
|
2731
2836
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
@@ -8766,11 +8871,11 @@ function normalizeGrepMax(value) {
|
|
|
8766
8871
|
return Math.min(parsed, GREP_ABSOLUTE_MAX);
|
|
8767
8872
|
}
|
|
8768
8873
|
function isSkippableSearchPath(path, omitDirNames = [], excludeNames = []) {
|
|
8769
|
-
const omitDirs =
|
|
8770
|
-
const excluded =
|
|
8874
|
+
const omitDirs = compileNamePatterns(omitDirNames);
|
|
8875
|
+
const excluded = compileNamePatterns(excludeNames);
|
|
8771
8876
|
return path.split(/[\\/]+/).some((part) => {
|
|
8772
8877
|
const lower = part.toLowerCase();
|
|
8773
|
-
return lower === ".git" || lower === ".code-viewer" || omitDirs.
|
|
8878
|
+
return lower === ".git" || lower === ".code-viewer" || omitDirs.matches(part) || excluded.matches(part);
|
|
8774
8879
|
});
|
|
8775
8880
|
}
|
|
8776
8881
|
function fixedStringLineMatches(path, text, query, max) {
|
|
@@ -8877,6 +8982,7 @@ function parseGitGrepOutput(stdout, ref, max, omitDirNames = [], excludeNames =
|
|
|
8877
8982
|
}
|
|
8878
8983
|
var GREP_DEFAULT_MAX = 200, GREP_ABSOLUTE_MAX = 500, GREP_MAX_FILE_BYTES, FILE_SEARCH_ABSOLUTE_MAX = 50000, DEFAULT_EXCLUDE_NAMES;
|
|
8879
8984
|
var init_search = __esm(() => {
|
|
8985
|
+
init_name_pattern();
|
|
8880
8986
|
GREP_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
8881
8987
|
DEFAULT_EXCLUDE_NAMES = [".DS_Store"];
|
|
8882
8988
|
});
|
|
@@ -10882,7 +10988,7 @@ function buildExecInvocation(config, sql) {
|
|
|
10882
10988
|
"-t",
|
|
10883
10989
|
"-A",
|
|
10884
10990
|
"-F",
|
|
10885
|
-
|
|
10991
|
+
PG_FIELD_SEPARATOR,
|
|
10886
10992
|
"-R",
|
|
10887
10993
|
PG_RECORD_SEPARATOR,
|
|
10888
10994
|
"-v",
|
|
@@ -11226,14 +11332,14 @@ function decodeMysqlBatchField(value) {
|
|
|
11226
11332
|
}
|
|
11227
11333
|
return out;
|
|
11228
11334
|
}
|
|
11229
|
-
function splitTsvLine(line, decodeFields) {
|
|
11230
|
-
const fields = line.split(
|
|
11335
|
+
function splitTsvLine(line, decodeFields, fieldSeparator = "\t") {
|
|
11336
|
+
const fields = line.split(fieldSeparator);
|
|
11231
11337
|
return decodeFields ? fields.map(decodeMysqlBatchField) : fields;
|
|
11232
11338
|
}
|
|
11233
11339
|
function stripFinalRecordSeparator(text, recordSeparator) {
|
|
11234
11340
|
return text.endsWith(recordSeparator) ? text.slice(0, -recordSeparator.length) : text;
|
|
11235
11341
|
}
|
|
11236
|
-
function parseTsvOutput(stdout, hasHeader, recordSeparator) {
|
|
11342
|
+
function parseTsvOutput(stdout, hasHeader, recordSeparator, fieldSeparator) {
|
|
11237
11343
|
const text = recordSeparator ? stripFinalRecordSeparator(stripFinalLineBreak(stdout), recordSeparator) : stripFinalLineBreak(stdout);
|
|
11238
11344
|
if (text.length === 0)
|
|
11239
11345
|
return { columns: [], rows: [] };
|
|
@@ -11241,11 +11347,11 @@ function parseTsvOutput(stdout, hasHeader, recordSeparator) {
|
|
|
11241
11347
|
if (lines.length === 0)
|
|
11242
11348
|
return { columns: [], rows: [] };
|
|
11243
11349
|
if (hasHeader) {
|
|
11244
|
-
const columns = splitTsvLine(lines[0], true);
|
|
11245
|
-
const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true));
|
|
11350
|
+
const columns = splitTsvLine(lines[0], true, fieldSeparator);
|
|
11351
|
+
const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true, fieldSeparator));
|
|
11246
11352
|
return { columns, rows: rows2 };
|
|
11247
11353
|
}
|
|
11248
|
-
const rows = lines.map((line) => splitTsvLine(line, false));
|
|
11354
|
+
const rows = lines.map((line) => splitTsvLine(line, false, fieldSeparator));
|
|
11249
11355
|
return { columns: [], rows };
|
|
11250
11356
|
}
|
|
11251
11357
|
function isMysqlSpatialType(type) {
|
|
@@ -11315,7 +11421,7 @@ function createSqlCliAdapter(config) {
|
|
|
11315
11421
|
if (result.code !== 0) {
|
|
11316
11422
|
throw new Error(result.stderr.trim() || "query failed");
|
|
11317
11423
|
}
|
|
11318
|
-
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
11424
|
+
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, config.kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
|
|
11319
11425
|
}
|
|
11320
11426
|
function toDbValue(val) {
|
|
11321
11427
|
if (val === "NULL" || val === "\\N")
|
|
@@ -11392,15 +11498,16 @@ function createSqlCliAdapter(config) {
|
|
|
11392
11498
|
async getTablesAsync(signal) {
|
|
11393
11499
|
let sql;
|
|
11394
11500
|
if (config.kind === "postgresql") {
|
|
11395
|
-
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ${postgresSchemaLiteral()} ORDER BY table_name`;
|
|
11501
|
+
sql = `SELECT t.table_name, t.table_type, COALESCE(obj_description(cls.oid, 'pg_class'), '') FROM information_schema.tables t JOIN pg_namespace n ON n.nspname = t.table_schema JOIN pg_class cls ON cls.relnamespace = n.oid AND cls.relname = t.table_name WHERE t.table_schema = ${postgresSchemaLiteral()} ORDER BY t.table_name`;
|
|
11396
11502
|
} else {
|
|
11397
|
-
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
|
|
11503
|
+
sql = `SELECT table_name, table_type, table_comment FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
|
|
11398
11504
|
}
|
|
11399
11505
|
const result = await execAsync(sql, signal);
|
|
11400
11506
|
return result.rows.map((row) => ({
|
|
11401
11507
|
name: row[0],
|
|
11402
11508
|
type: row[1] === "VIEW" ? "view" : "table",
|
|
11403
|
-
rowCount: null
|
|
11509
|
+
rowCount: null,
|
|
11510
|
+
comment: row[2] || null
|
|
11404
11511
|
}));
|
|
11405
11512
|
},
|
|
11406
11513
|
async getColumnsAsync(table, signal) {
|
|
@@ -11413,11 +11520,11 @@ function createSqlCliAdapter(config) {
|
|
|
11413
11520
|
},
|
|
11414
11521
|
async getIndexesAsync(signal) {
|
|
11415
11522
|
let sql;
|
|
11416
|
-
const INDEX_COL_SEP = "\
|
|
11523
|
+
const INDEX_COL_SEP = "\x1D";
|
|
11417
11524
|
if (config.kind === "postgresql") {
|
|
11418
|
-
sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\
|
|
11525
|
+
sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\x1d' ORDER BY k.ord), '') FROM pg_index ix JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = ${postgresSchemaLiteral()} AND i.relname NOT LIKE 'pg_%' GROUP BY i.relname, t.relname, ix.indisunique ORDER BY t.relname, i.relname`;
|
|
11419
11526
|
} else {
|
|
11420
|
-
sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\
|
|
11527
|
+
sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\x1D') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
|
|
11421
11528
|
}
|
|
11422
11529
|
const result = await execAsync(sql, signal);
|
|
11423
11530
|
return result.rows.map((row) => ({
|
|
@@ -11818,7 +11925,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
11818
11925
|
return fallback;
|
|
11819
11926
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
11820
11927
|
}
|
|
11821
|
-
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
11928
|
+
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
|
|
11822
11929
|
const dbs = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
11823
11930
|
const value = dbs.length > 0 ? dbs : fallbackDockerDatabases(defaultDb);
|
|
11824
11931
|
return setDockerDatabasesCache(cacheKey, value, value.length > 0 ? DOCKER_DATABASES_POSITIVE_TTL_MS : DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
@@ -11858,7 +11965,7 @@ async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, sign
|
|
|
11858
11965
|
if (result.code !== 0) {
|
|
11859
11966
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
11860
11967
|
}
|
|
11861
|
-
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR);
|
|
11968
|
+
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR, PG_FIELD_SEPARATOR);
|
|
11862
11969
|
const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
11863
11970
|
const value = schemas.length > 0 ? schemas : ["public"];
|
|
11864
11971
|
return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
|
|
@@ -11916,7 +12023,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
11916
12023
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
11917
12024
|
});
|
|
11918
12025
|
}
|
|
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";
|
|
12026
|
+
var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config), COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", PG_FIELD_SEPARATOR = "\x1F", MYSQL_SPATIAL_TYPES, createDockerAdapter, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
|
|
11920
12027
|
var init_docker = __esm(() => {
|
|
11921
12028
|
init_mutate();
|
|
11922
12029
|
init_sql_snapshot();
|
|
@@ -23140,7 +23247,8 @@ async function rgAvailableAsync(cwd) {
|
|
|
23140
23247
|
return rgAvailableCache;
|
|
23141
23248
|
}
|
|
23142
23249
|
function isExcludedScopePath(path, excludeNames) {
|
|
23143
|
-
|
|
23250
|
+
const excluded = compileNamePatterns(excludeNames);
|
|
23251
|
+
return path.split(/[\\/]+/).some((part) => excluded.matches(part));
|
|
23144
23252
|
}
|
|
23145
23253
|
function isSafePath(path) {
|
|
23146
23254
|
if (!path || path.startsWith("/") || path.startsWith("\\") || path.includes("\x00"))
|
|
@@ -23339,6 +23447,7 @@ var init_search_service = __esm(() => {
|
|
|
23339
23447
|
init_command_resolver();
|
|
23340
23448
|
init_spawn_runner();
|
|
23341
23449
|
init_git();
|
|
23450
|
+
init_name_pattern();
|
|
23342
23451
|
init_search();
|
|
23343
23452
|
});
|
|
23344
23453
|
|
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -10480,6 +10480,80 @@ ${frontmatter.yaml}
|
|
|
10480
10480
|
return true;
|
|
10481
10481
|
}
|
|
10482
10482
|
|
|
10483
|
+
// web-src/views/database/detail-table.ts
|
|
10484
|
+
function createDetailTable(headers, rows, emptyText) {
|
|
10485
|
+
const table2 = document.createElement("table");
|
|
10486
|
+
table2.className = "db-detail-table";
|
|
10487
|
+
const thead = document.createElement("thead");
|
|
10488
|
+
const headRow = document.createElement("tr");
|
|
10489
|
+
for (const label of headers) {
|
|
10490
|
+
const th = document.createElement("th");
|
|
10491
|
+
th.textContent = label;
|
|
10492
|
+
headRow.appendChild(th);
|
|
10493
|
+
}
|
|
10494
|
+
thead.appendChild(headRow);
|
|
10495
|
+
table2.appendChild(thead);
|
|
10496
|
+
const tbody = document.createElement("tbody");
|
|
10497
|
+
if (rows.length === 0) {
|
|
10498
|
+
const row = document.createElement("tr");
|
|
10499
|
+
const td = document.createElement("td");
|
|
10500
|
+
td.colSpan = headers.length;
|
|
10501
|
+
td.className = "db-value-empty";
|
|
10502
|
+
td.textContent = emptyText;
|
|
10503
|
+
row.appendChild(td);
|
|
10504
|
+
tbody.appendChild(row);
|
|
10505
|
+
}
|
|
10506
|
+
for (const cells of rows) {
|
|
10507
|
+
const row = document.createElement("tr");
|
|
10508
|
+
cells.forEach((cellText, index) => {
|
|
10509
|
+
const td = document.createElement("td");
|
|
10510
|
+
td.textContent = cellText;
|
|
10511
|
+
td.className = index === 0 ? "db-detail-table-primary" : "db-detail-table-muted";
|
|
10512
|
+
row.appendChild(td);
|
|
10513
|
+
});
|
|
10514
|
+
tbody.appendChild(row);
|
|
10515
|
+
}
|
|
10516
|
+
table2.appendChild(tbody);
|
|
10517
|
+
return table2;
|
|
10518
|
+
}
|
|
10519
|
+
|
|
10520
|
+
// web-src/views/database/detail-tabs.ts
|
|
10521
|
+
function createDetailTabs(specs, initial, onSelect) {
|
|
10522
|
+
const tabsEl = document.createElement("div");
|
|
10523
|
+
tabsEl.className = "db-detail-tabs";
|
|
10524
|
+
const buttons = {};
|
|
10525
|
+
const bodies = {};
|
|
10526
|
+
let active = initial;
|
|
10527
|
+
function setActive(tab) {
|
|
10528
|
+
active = tab;
|
|
10529
|
+
for (const spec of specs) {
|
|
10530
|
+
buttons[spec.id].classList.toggle("active", spec.id === tab);
|
|
10531
|
+
bodies[spec.id].hidden = spec.id !== tab;
|
|
10532
|
+
}
|
|
10533
|
+
}
|
|
10534
|
+
for (const spec of specs) {
|
|
10535
|
+
const btn = document.createElement("button");
|
|
10536
|
+
btn.type = "button";
|
|
10537
|
+
btn.className = "db-detail-tab";
|
|
10538
|
+
btn.textContent = spec.label;
|
|
10539
|
+
btn.addEventListener("click", () => {
|
|
10540
|
+
setActive(spec.id);
|
|
10541
|
+
onSelect(spec.id);
|
|
10542
|
+
});
|
|
10543
|
+
buttons[spec.id] = btn;
|
|
10544
|
+
tabsEl.appendChild(btn);
|
|
10545
|
+
const body = document.createElement("div");
|
|
10546
|
+
body.className = "db-detail-tab-body";
|
|
10547
|
+
bodies[spec.id] = body;
|
|
10548
|
+
}
|
|
10549
|
+
setActive(initial);
|
|
10550
|
+
function setLabels(labels) {
|
|
10551
|
+
for (const spec of specs)
|
|
10552
|
+
buttons[spec.id].textContent = labels[spec.id];
|
|
10553
|
+
}
|
|
10554
|
+
return { tabsEl, bodies, getActive: () => active, setActive, setLabels };
|
|
10555
|
+
}
|
|
10556
|
+
|
|
10483
10557
|
// web-src/views/database/i18n.ts
|
|
10484
10558
|
var EN = {
|
|
10485
10559
|
nav: {
|
|
@@ -10820,7 +10894,21 @@ ${frontmatter.yaml}
|
|
|
10820
10894
|
copied: "Copied",
|
|
10821
10895
|
copyFailed: "Copy failed",
|
|
10822
10896
|
invalidAttributeValues: "Invalid attribute values JSON",
|
|
10823
|
-
runQuery: "Run"
|
|
10897
|
+
runQuery: "Run",
|
|
10898
|
+
structureTab: "Structure",
|
|
10899
|
+
itemTab: "Item",
|
|
10900
|
+
selectTable: "Select a table to view its structure.",
|
|
10901
|
+
attributeHeader: "Attribute",
|
|
10902
|
+
typeHeader: "Type",
|
|
10903
|
+
keyRoleHeader: "Key",
|
|
10904
|
+
noAttributes: "(no attributes)",
|
|
10905
|
+
globalSecondaryIndexes: "Global secondary indexes",
|
|
10906
|
+
localSecondaryIndexes: "Local secondary indexes",
|
|
10907
|
+
projectionAll: "ALL",
|
|
10908
|
+
projectionKeysOnly: "KEYS_ONLY",
|
|
10909
|
+
projectionInclude: (attrs) => `INCLUDE (${attrs})`,
|
|
10910
|
+
keySchemaOnlyHint: "DynamoDB only enforces types for key attributes. Additional attributes will appear here once items are loaded.",
|
|
10911
|
+
inferredAttributesNote: (count) => `Attributes beyond the key schema are inferred from ${count.toLocaleString()} loaded item${count === 1 ? "" : "s"} and may not reflect every item.`
|
|
10824
10912
|
}
|
|
10825
10913
|
}
|
|
10826
10914
|
};
|
|
@@ -11163,7 +11251,21 @@ ${frontmatter.yaml}
|
|
|
11163
11251
|
copied: "コピーしました",
|
|
11164
11252
|
copyFailed: "コピーに失敗しました",
|
|
11165
11253
|
invalidAttributeValues: "属性値の JSON が不正です",
|
|
11166
|
-
runQuery: "実行"
|
|
11254
|
+
runQuery: "実行",
|
|
11255
|
+
structureTab: "構造",
|
|
11256
|
+
itemTab: "アイテム",
|
|
11257
|
+
selectTable: "テーブルを選択すると構造が表示されます。",
|
|
11258
|
+
attributeHeader: "属性",
|
|
11259
|
+
typeHeader: "型",
|
|
11260
|
+
keyRoleHeader: "キー",
|
|
11261
|
+
noAttributes: "(属性がありません)",
|
|
11262
|
+
globalSecondaryIndexes: "グローバルセカンダリインデックス",
|
|
11263
|
+
localSecondaryIndexes: "ローカルセカンダリインデックス",
|
|
11264
|
+
projectionAll: "ALL",
|
|
11265
|
+
projectionKeysOnly: "KEYS_ONLY",
|
|
11266
|
+
projectionInclude: (attrs) => `INCLUDE (${attrs})`,
|
|
11267
|
+
keySchemaOnlyHint: "DynamoDBがスキーマとして強制するのはキー属性のみです。アイテムを読み込むと追加の属性がここに表示されます。",
|
|
11268
|
+
inferredAttributesNote: (count) => `キー以外の属性は、読み込み済みの${count.toLocaleString()}件のアイテムから検出したものです (全アイテムを網羅するとは限りません)。`
|
|
11167
11269
|
}
|
|
11168
11270
|
}
|
|
11169
11271
|
};
|
|
@@ -11237,6 +11339,25 @@ ${frontmatter.yaml}
|
|
|
11237
11339
|
out[k] = unwrapAttributeValue(v);
|
|
11238
11340
|
return out;
|
|
11239
11341
|
}
|
|
11342
|
+
var ATTRIBUTE_VALUE_TAG_ORDER = [
|
|
11343
|
+
"S",
|
|
11344
|
+
"N",
|
|
11345
|
+
"B",
|
|
11346
|
+
"BOOL",
|
|
11347
|
+
"NULL",
|
|
11348
|
+
"M",
|
|
11349
|
+
"L",
|
|
11350
|
+
"SS",
|
|
11351
|
+
"NS",
|
|
11352
|
+
"BS"
|
|
11353
|
+
];
|
|
11354
|
+
function attributeValueTag(av) {
|
|
11355
|
+
for (const tag of ATTRIBUTE_VALUE_TAG_ORDER) {
|
|
11356
|
+
if (tag in av)
|
|
11357
|
+
return tag;
|
|
11358
|
+
}
|
|
11359
|
+
return "?";
|
|
11360
|
+
}
|
|
11240
11361
|
function previewItem(item) {
|
|
11241
11362
|
const entries = Object.entries(item).slice(0, 6);
|
|
11242
11363
|
const parts = entries.map(([key, rawValue]) => {
|
|
@@ -11347,8 +11468,17 @@ ${frontmatter.yaml}
|
|
|
11347
11468
|
moreBtn.hidden = true;
|
|
11348
11469
|
itemListPane.appendChild(moreBtn);
|
|
11349
11470
|
const detailPane = document.createElement("div");
|
|
11350
|
-
detailPane.className = "
|
|
11351
|
-
|
|
11471
|
+
detailPane.className = "db-detail-pane";
|
|
11472
|
+
const detailTabs = createDetailTabs([
|
|
11473
|
+
{ id: "structure", label: text3().dynamodb.structureTab },
|
|
11474
|
+
{ id: "item", label: text3().dynamodb.itemTab }
|
|
11475
|
+
], "structure", () => notifySelectionChange());
|
|
11476
|
+
detailPane.appendChild(detailTabs.tabsEl);
|
|
11477
|
+
const structureBody = detailTabs.bodies.structure;
|
|
11478
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
11479
|
+
const itemBody = detailTabs.bodies.item;
|
|
11480
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
11481
|
+
detailPane.append(structureBody, itemBody);
|
|
11352
11482
|
container.append(itemListPane, detailPane);
|
|
11353
11483
|
let currentDbId = null;
|
|
11354
11484
|
let currentTable = null;
|
|
@@ -11360,6 +11490,7 @@ ${frontmatter.yaml}
|
|
|
11360
11490
|
let currentItemKeyToken = null;
|
|
11361
11491
|
let cumulativeShownCount = 0;
|
|
11362
11492
|
let cumulativeScannedCount = 0;
|
|
11493
|
+
let lastRenderedItem = null;
|
|
11363
11494
|
let disposed = false;
|
|
11364
11495
|
let loadRunId = 0;
|
|
11365
11496
|
let itemRunId = 0;
|
|
@@ -11384,6 +11515,9 @@ ${frontmatter.yaml}
|
|
|
11384
11515
|
queryModeBtn.classList.toggle("active", mode === "query");
|
|
11385
11516
|
keyConditionInput.hidden = mode !== "query";
|
|
11386
11517
|
}
|
|
11518
|
+
function setDetailTab(tab) {
|
|
11519
|
+
detailTabs.setActive(tab);
|
|
11520
|
+
}
|
|
11387
11521
|
function renderTables(tableNames, append = false) {
|
|
11388
11522
|
if (!append) {
|
|
11389
11523
|
tableList.innerHTML = "";
|
|
@@ -11436,10 +11570,51 @@ ${frontmatter.yaml}
|
|
|
11436
11570
|
}
|
|
11437
11571
|
itemList.appendChild(fragment);
|
|
11438
11572
|
}
|
|
11439
|
-
function
|
|
11440
|
-
|
|
11441
|
-
if (!
|
|
11573
|
+
function projectionText(projection) {
|
|
11574
|
+
const t2 = text3().dynamodb;
|
|
11575
|
+
if (!projection?.ProjectionType)
|
|
11576
|
+
return "";
|
|
11577
|
+
if (projection.ProjectionType === "ALL")
|
|
11578
|
+
return t2.projectionAll;
|
|
11579
|
+
if (projection.ProjectionType === "KEYS_ONLY")
|
|
11580
|
+
return t2.projectionKeysOnly;
|
|
11581
|
+
return t2.projectionInclude((projection.NonKeyAttributes ?? []).join(", "));
|
|
11582
|
+
}
|
|
11583
|
+
function renderSecondaryIndexes(label, indexes) {
|
|
11584
|
+
if (!indexes || indexes.length === 0)
|
|
11442
11585
|
return;
|
|
11586
|
+
const section = document.createElement("div");
|
|
11587
|
+
section.className = "dynamodb-index-section";
|
|
11588
|
+
const heading2 = document.createElement("div");
|
|
11589
|
+
heading2.className = "dynamodb-index-heading";
|
|
11590
|
+
heading2.textContent = label;
|
|
11591
|
+
section.appendChild(heading2);
|
|
11592
|
+
for (const ix of indexes) {
|
|
11593
|
+
const row = document.createElement("div");
|
|
11594
|
+
row.className = "dynamodb-index-row";
|
|
11595
|
+
const name = document.createElement("div");
|
|
11596
|
+
name.className = "dynamodb-index-name";
|
|
11597
|
+
name.textContent = ix.IndexName ?? "";
|
|
11598
|
+
const detail = document.createElement("div");
|
|
11599
|
+
detail.className = "dynamodb-index-detail";
|
|
11600
|
+
const keyPart = (ix.KeySchema ?? []).map((k) => `${k.AttributeName} (${k.KeyType})`).join(", ");
|
|
11601
|
+
detail.textContent = [
|
|
11602
|
+
keyPart,
|
|
11603
|
+
projectionText(ix.Projection),
|
|
11604
|
+
ix.ItemCount !== undefined ? `${ix.ItemCount.toLocaleString()} items` : undefined
|
|
11605
|
+
].filter(Boolean).join(" / ");
|
|
11606
|
+
row.append(name, detail);
|
|
11607
|
+
section.appendChild(row);
|
|
11608
|
+
}
|
|
11609
|
+
structureBody.appendChild(section);
|
|
11610
|
+
}
|
|
11611
|
+
function renderTableStructure() {
|
|
11612
|
+
structureBody.innerHTML = "";
|
|
11613
|
+
if (!currentTableInfo) {
|
|
11614
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
11615
|
+
return;
|
|
11616
|
+
}
|
|
11617
|
+
const t2 = text3().dynamodb;
|
|
11443
11618
|
const header = document.createElement("div");
|
|
11444
11619
|
header.className = "dynamodb-table-info-header";
|
|
11445
11620
|
header.textContent = currentTableInfo.TableName ?? currentTable ?? "";
|
|
@@ -11447,26 +11622,54 @@ ${frontmatter.yaml}
|
|
|
11447
11622
|
meta.className = "dynamodb-table-info-meta";
|
|
11448
11623
|
meta.textContent = [
|
|
11449
11624
|
currentTableInfo.TableStatus,
|
|
11450
|
-
currentTableInfo.ItemCount !== undefined ? `${currentTableInfo.ItemCount.toLocaleString()} items` : undefined
|
|
11625
|
+
currentTableInfo.ItemCount !== undefined ? `${currentTableInfo.ItemCount.toLocaleString()} items` : undefined,
|
|
11626
|
+
currentTableInfo.TableSizeBytes !== undefined ? formatBytes(currentTableInfo.TableSizeBytes) : undefined,
|
|
11627
|
+
currentTableInfo.BillingModeSummary?.BillingMode
|
|
11451
11628
|
].filter(Boolean).join(" / ");
|
|
11452
|
-
|
|
11453
|
-
const
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
const
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11629
|
+
structureBody.append(header, meta);
|
|
11630
|
+
const attributeTypeByName = new Map((currentTableInfo.AttributeDefinitions ?? []).map((a2) => [
|
|
11631
|
+
a2.AttributeName,
|
|
11632
|
+
a2.AttributeType
|
|
11633
|
+
]));
|
|
11634
|
+
const keyRoleByName = new Map((currentTableInfo.KeySchema ?? []).map((k) => [
|
|
11635
|
+
k.AttributeName,
|
|
11636
|
+
k.KeyType
|
|
11637
|
+
]));
|
|
11638
|
+
const attrNames = [...attributeTypeByName.keys()];
|
|
11639
|
+
const inferredTypesByName = new Map;
|
|
11640
|
+
for (const item of itemsByKeyToken.values()) {
|
|
11641
|
+
for (const [attrName, av] of Object.entries(item)) {
|
|
11642
|
+
if (attributeTypeByName.has(attrName))
|
|
11643
|
+
continue;
|
|
11644
|
+
const set2 = inferredTypesByName.get(attrName) ?? new Set;
|
|
11645
|
+
set2.add(attributeValueTag(av));
|
|
11646
|
+
inferredTypesByName.set(attrName, set2);
|
|
11647
|
+
}
|
|
11648
|
+
}
|
|
11649
|
+
const inferredNames = [...inferredTypesByName.keys()].sort();
|
|
11650
|
+
const rows = [
|
|
11651
|
+
...attrNames.map((name) => [
|
|
11652
|
+
name,
|
|
11653
|
+
attributeTypeByName.get(name) ?? "",
|
|
11654
|
+
keyRoleByName.get(name) ?? ""
|
|
11655
|
+
]),
|
|
11656
|
+
...inferredNames.map((name) => [
|
|
11657
|
+
name,
|
|
11658
|
+
[...inferredTypesByName.get(name) ?? []].join(", "),
|
|
11659
|
+
""
|
|
11660
|
+
])
|
|
11661
|
+
];
|
|
11662
|
+
structureBody.appendChild(createDetailTable([t2.attributeHeader, t2.typeHeader, t2.keyRoleHeader], rows, t2.noAttributes));
|
|
11663
|
+
const note = document.createElement("div");
|
|
11664
|
+
note.className = "dynamodb-attr-note";
|
|
11665
|
+
note.textContent = inferredNames.length === 0 ? t2.keySchemaOnlyHint : t2.inferredAttributesNote(itemsByKeyToken.size);
|
|
11666
|
+
structureBody.appendChild(note);
|
|
11667
|
+
renderSecondaryIndexes(t2.globalSecondaryIndexes, currentTableInfo.GlobalSecondaryIndexes);
|
|
11668
|
+
renderSecondaryIndexes(t2.localSecondaryIndexes, currentTableInfo.LocalSecondaryIndexes);
|
|
11467
11669
|
}
|
|
11468
11670
|
function renderItemDetail(item) {
|
|
11469
|
-
|
|
11671
|
+
lastRenderedItem = item;
|
|
11672
|
+
itemBody.innerHTML = "";
|
|
11470
11673
|
const header = document.createElement("div");
|
|
11471
11674
|
header.className = "dynamodb-item-detail-header";
|
|
11472
11675
|
const title = document.createElement("span");
|
|
@@ -11489,7 +11692,7 @@ ${frontmatter.yaml}
|
|
|
11489
11692
|
}
|
|
11490
11693
|
});
|
|
11491
11694
|
header.append(title, copyBtn, copyStatus);
|
|
11492
|
-
|
|
11695
|
+
itemBody.appendChild(header);
|
|
11493
11696
|
const pre = document.createElement("pre");
|
|
11494
11697
|
pre.className = "dynamodb-item-source";
|
|
11495
11698
|
try {
|
|
@@ -11497,12 +11700,13 @@ ${frontmatter.yaml}
|
|
|
11497
11700
|
} catch {
|
|
11498
11701
|
pre.textContent = String(item);
|
|
11499
11702
|
}
|
|
11500
|
-
|
|
11703
|
+
itemBody.appendChild(pre);
|
|
11501
11704
|
}
|
|
11502
11705
|
function selectItem(item, token) {
|
|
11503
11706
|
currentItemKeyToken = token ?? itemKeyToken(extractItemKey(item, currentTableInfo?.KeySchema));
|
|
11504
11707
|
highlightActiveItem(currentItemKeyToken);
|
|
11505
11708
|
renderItemDetail(item);
|
|
11709
|
+
setDetailTab("item");
|
|
11506
11710
|
notifySelectionChange();
|
|
11507
11711
|
}
|
|
11508
11712
|
async function loadItems(append) {
|
|
@@ -11538,8 +11742,6 @@ ${frontmatter.yaml}
|
|
|
11538
11742
|
cumulativeShownCount = 0;
|
|
11539
11743
|
cumulativeScannedCount = 0;
|
|
11540
11744
|
setPaneStatus(itemList, "Loading items...");
|
|
11541
|
-
if (!currentTableInfo)
|
|
11542
|
-
setPaneEmpty(detailPane, text3().dynamodb.selectItem);
|
|
11543
11745
|
}
|
|
11544
11746
|
try {
|
|
11545
11747
|
const params = new URLSearchParams({
|
|
@@ -11581,6 +11783,8 @@ ${frontmatter.yaml}
|
|
|
11581
11783
|
} else {
|
|
11582
11784
|
appendItems(data.items);
|
|
11583
11785
|
}
|
|
11786
|
+
if (currentTableInfo)
|
|
11787
|
+
renderTableStructure();
|
|
11584
11788
|
currentNextToken = data.lastEvaluatedKey;
|
|
11585
11789
|
moreBtn.hidden = !data.lastEvaluatedKey;
|
|
11586
11790
|
cumulativeShownCount += data.items.length;
|
|
@@ -11602,21 +11806,29 @@ ${frontmatter.yaml}
|
|
|
11602
11806
|
return;
|
|
11603
11807
|
const slot = tableInfoGuard.start();
|
|
11604
11808
|
const requestDbId = currentDbId;
|
|
11809
|
+
const isStaleRequest = () => disposed || slot.isStale() || requestDbId !== currentDbId || currentTable !== table2;
|
|
11605
11810
|
try {
|
|
11606
11811
|
const params = new URLSearchParams({ db: requestDbId, table: table2 });
|
|
11607
11812
|
const res = await trackLoad(fetch(`/_db/dynamodb/table?${params}`, { signal: slot.signal }));
|
|
11608
|
-
if (
|
|
11813
|
+
if (isStaleRequest())
|
|
11609
11814
|
return;
|
|
11610
|
-
if (!res.ok)
|
|
11815
|
+
if (!res.ok) {
|
|
11816
|
+
const errText = await res.text();
|
|
11817
|
+
setPaneStatus(structureBody, `Error: ${errText || res.statusText}`, {
|
|
11818
|
+
error: true
|
|
11819
|
+
});
|
|
11611
11820
|
return;
|
|
11821
|
+
}
|
|
11612
11822
|
const data = await res.json();
|
|
11613
|
-
if (
|
|
11823
|
+
if (isStaleRequest())
|
|
11614
11824
|
return;
|
|
11615
|
-
}
|
|
11616
11825
|
currentTableInfo = data.table;
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11826
|
+
renderTableStructure();
|
|
11827
|
+
} catch (err) {
|
|
11828
|
+
if (isStaleRequest())
|
|
11829
|
+
return;
|
|
11830
|
+
setPaneStatus(structureBody, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
|
|
11831
|
+
} finally {
|
|
11620
11832
|
slot.finish();
|
|
11621
11833
|
}
|
|
11622
11834
|
}
|
|
@@ -11656,7 +11868,14 @@ ${frontmatter.yaml}
|
|
|
11656
11868
|
currentNextToken = undefined;
|
|
11657
11869
|
highlightActiveTable(name);
|
|
11658
11870
|
notifySelectionChange();
|
|
11659
|
-
|
|
11871
|
+
lastRenderedItem = null;
|
|
11872
|
+
itemsByKeyToken.clear();
|
|
11873
|
+
itemRowsByKeyToken.clear();
|
|
11874
|
+
structureBody.innerHTML = "";
|
|
11875
|
+
setPaneStatus(structureBody, "Loading table...");
|
|
11876
|
+
itemBody.innerHTML = "";
|
|
11877
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
11878
|
+
setDetailTab("structure");
|
|
11660
11879
|
await fetchTableInfo(name);
|
|
11661
11880
|
await loadItems(false);
|
|
11662
11881
|
}
|
|
@@ -11773,7 +11992,12 @@ ${frontmatter.yaml}
|
|
|
11773
11992
|
activeItemRow = null;
|
|
11774
11993
|
moreBtn.hidden = true;
|
|
11775
11994
|
tableMoreBtn.hidden = true;
|
|
11776
|
-
|
|
11995
|
+
lastRenderedItem = null;
|
|
11996
|
+
structureBody.innerHTML = "";
|
|
11997
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
11998
|
+
itemBody.innerHTML = "";
|
|
11999
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
12000
|
+
setDetailTab("structure");
|
|
11777
12001
|
setPaneStatus(tableList, "Loading tables...");
|
|
11778
12002
|
try {
|
|
11779
12003
|
const res = await trackLoad(fetch(`/_db/dynamodb/tables?db=${encodeURIComponent(dbId)}`, {
|
|
@@ -11809,6 +12033,8 @@ ${frontmatter.yaml}
|
|
|
11809
12033
|
await selectItemByKey(key);
|
|
11810
12034
|
} catch {}
|
|
11811
12035
|
}
|
|
12036
|
+
if (initial?.detailTab)
|
|
12037
|
+
setDetailTab(initial.detailTab);
|
|
11812
12038
|
} finally {
|
|
11813
12039
|
suppressNotify = false;
|
|
11814
12040
|
}
|
|
@@ -11854,7 +12080,12 @@ ${frontmatter.yaml}
|
|
|
11854
12080
|
itemRowsByKeyToken.clear();
|
|
11855
12081
|
activeItemRow = null;
|
|
11856
12082
|
moreBtn.hidden = true;
|
|
11857
|
-
|
|
12083
|
+
lastRenderedItem = null;
|
|
12084
|
+
structureBody.innerHTML = "";
|
|
12085
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
12086
|
+
itemBody.innerHTML = "";
|
|
12087
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
12088
|
+
setDetailTab("structure");
|
|
11858
12089
|
}
|
|
11859
12090
|
setMode("scan");
|
|
11860
12091
|
function getSelection() {
|
|
@@ -11865,7 +12096,8 @@ ${frontmatter.yaml}
|
|
|
11865
12096
|
filterExpression: filterInput.value.trim() || undefined,
|
|
11866
12097
|
expressionAttributeValues: attributeValuesInput.value.trim() || undefined,
|
|
11867
12098
|
scanIndexForward: currentMode === "query" ? currentScanIndexForward : undefined,
|
|
11868
|
-
itemKey: currentItemKeyToken ?? undefined
|
|
12099
|
+
itemKey: currentItemKeyToken ?? undefined,
|
|
12100
|
+
detailTab: detailTabs.getActive()
|
|
11869
12101
|
};
|
|
11870
12102
|
}
|
|
11871
12103
|
function dispose() {
|
|
@@ -11883,12 +12115,18 @@ ${frontmatter.yaml}
|
|
|
11883
12115
|
runBtn.textContent = t2.dynamodb.runQuery;
|
|
11884
12116
|
tableMoreBtn.textContent = t2.common.loadMore;
|
|
11885
12117
|
moreBtn.textContent = t2.common.loadMore;
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
12118
|
+
detailTabs.setLabels({
|
|
12119
|
+
structure: t2.dynamodb.structureTab,
|
|
12120
|
+
item: t2.dynamodb.itemTab
|
|
12121
|
+
});
|
|
12122
|
+
if (currentTableInfo)
|
|
12123
|
+
renderTableStructure();
|
|
12124
|
+
else
|
|
12125
|
+
setPaneEmpty(structureBody, t2.dynamodb.selectTable);
|
|
12126
|
+
if (lastRenderedItem)
|
|
12127
|
+
renderItemDetail(lastRenderedItem);
|
|
12128
|
+
else
|
|
12129
|
+
setPaneEmpty(itemBody, t2.dynamodb.selectItem);
|
|
11892
12130
|
}
|
|
11893
12131
|
return {
|
|
11894
12132
|
el: container,
|
|
@@ -11951,25 +12189,15 @@ ${frontmatter.yaml}
|
|
|
11951
12189
|
docMoreBtn.hidden = true;
|
|
11952
12190
|
docListPane.appendChild(docMoreBtn);
|
|
11953
12191
|
const detailPane = document.createElement("div");
|
|
11954
|
-
detailPane.className = "
|
|
11955
|
-
const detailTabs =
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
const tabDoc = document.createElement("button");
|
|
11962
|
-
tabDoc.type = "button";
|
|
11963
|
-
tabDoc.className = "es-detail-tab";
|
|
11964
|
-
tabDoc.textContent = text3().es.doc;
|
|
11965
|
-
detailTabs.append(tabMapping, tabDoc);
|
|
11966
|
-
detailPane.appendChild(detailTabs);
|
|
11967
|
-
const mappingBody = document.createElement("div");
|
|
11968
|
-
mappingBody.className = "es-mapping-body";
|
|
12192
|
+
detailPane.className = "db-detail-pane";
|
|
12193
|
+
const detailTabs = createDetailTabs([
|
|
12194
|
+
{ id: "mapping", label: text3().es.mapping },
|
|
12195
|
+
{ id: "doc", label: text3().es.doc }
|
|
12196
|
+
], "mapping", () => {});
|
|
12197
|
+
detailPane.appendChild(detailTabs.tabsEl);
|
|
12198
|
+
const mappingBody = detailTabs.bodies.mapping;
|
|
11969
12199
|
setPaneEmpty(mappingBody, text3().es.selectIndex);
|
|
11970
|
-
const docBody =
|
|
11971
|
-
docBody.className = "es-doc-body";
|
|
11972
|
-
docBody.hidden = true;
|
|
12200
|
+
const docBody = detailTabs.bodies.doc;
|
|
11973
12201
|
setPaneEmpty(docBody, text3().es.selectDoc);
|
|
11974
12202
|
detailPane.append(mappingBody, docBody);
|
|
11975
12203
|
container.append(docListPane, detailPane);
|
|
@@ -12094,13 +12322,8 @@ ${frontmatter.yaml}
|
|
|
12094
12322
|
}
|
|
12095
12323
|
function setDetailTab(tab) {
|
|
12096
12324
|
detailTab = tab;
|
|
12097
|
-
|
|
12098
|
-
tabDoc.classList.toggle("active", tab === "doc");
|
|
12099
|
-
mappingBody.hidden = tab !== "mapping";
|
|
12100
|
-
docBody.hidden = tab !== "doc";
|
|
12325
|
+
detailTabs.setActive(tab);
|
|
12101
12326
|
}
|
|
12102
|
-
tabMapping.addEventListener("click", () => setDetailTab("mapping"));
|
|
12103
|
-
tabDoc.addEventListener("click", () => setDetailTab("doc"));
|
|
12104
12327
|
function renderMapping(resp) {
|
|
12105
12328
|
lastMapping = resp;
|
|
12106
12329
|
mappingBody.innerHTML = "";
|
|
@@ -12108,43 +12331,13 @@ ${frontmatter.yaml}
|
|
|
12108
12331
|
header.className = "es-mapping-header";
|
|
12109
12332
|
header.textContent = resp.mapping.index;
|
|
12110
12333
|
mappingBody.appendChild(header);
|
|
12111
|
-
const table2 = document.createElement("table");
|
|
12112
|
-
table2.className = "es-mapping-table";
|
|
12113
|
-
const thead = document.createElement("thead");
|
|
12114
|
-
const headRow = document.createElement("tr");
|
|
12115
|
-
for (const label of [text3().es.fieldHeader, text3().es.typeHeader]) {
|
|
12116
|
-
const th = document.createElement("th");
|
|
12117
|
-
th.textContent = label;
|
|
12118
|
-
headRow.appendChild(th);
|
|
12119
|
-
}
|
|
12120
|
-
thead.appendChild(headRow);
|
|
12121
|
-
table2.appendChild(thead);
|
|
12122
|
-
const tbody = document.createElement("tbody");
|
|
12123
12334
|
const props = resp.mapping.properties;
|
|
12124
12335
|
const keys = Object.keys(props).sort();
|
|
12125
|
-
|
|
12126
|
-
const row = document.createElement("tr");
|
|
12127
|
-
const td = document.createElement("td");
|
|
12128
|
-
td.colSpan = 2;
|
|
12129
|
-
td.className = "es-value-empty";
|
|
12130
|
-
td.textContent = text3().es.noMappedFields;
|
|
12131
|
-
row.appendChild(td);
|
|
12132
|
-
tbody.appendChild(row);
|
|
12133
|
-
}
|
|
12134
|
-
for (const key of keys) {
|
|
12135
|
-
const row = document.createElement("tr");
|
|
12136
|
-
const fieldTd = document.createElement("td");
|
|
12137
|
-
fieldTd.className = "es-mapping-field";
|
|
12138
|
-
fieldTd.textContent = key;
|
|
12139
|
-
const typeTd = document.createElement("td");
|
|
12140
|
-
typeTd.className = "es-mapping-type";
|
|
12336
|
+
const rows = keys.map((key) => {
|
|
12141
12337
|
const p2 = props[key];
|
|
12142
|
-
|
|
12143
|
-
|
|
12144
|
-
|
|
12145
|
-
}
|
|
12146
|
-
table2.appendChild(tbody);
|
|
12147
|
-
mappingBody.appendChild(table2);
|
|
12338
|
+
return [key, p2.type ?? (p2.properties ? "object" : "(unknown)")];
|
|
12339
|
+
});
|
|
12340
|
+
mappingBody.appendChild(createDetailTable([text3().es.fieldHeader, text3().es.typeHeader], rows, text3().es.noMappedFields));
|
|
12148
12341
|
}
|
|
12149
12342
|
function mkBtn(label, cls) {
|
|
12150
12343
|
const b2 = document.createElement("button");
|
|
@@ -12649,11 +12842,10 @@ ${frontmatter.yaml}
|
|
|
12649
12842
|
searchInput.placeholder = t2.es.queryPlaceholder;
|
|
12650
12843
|
searchBtn.textContent = t2.common.search;
|
|
12651
12844
|
docMoreBtn.textContent = t2.common.loadMore;
|
|
12652
|
-
|
|
12653
|
-
tabDoc.textContent = t2.es.doc;
|
|
12845
|
+
detailTabs.setLabels({ mapping: t2.es.mapping, doc: t2.es.doc });
|
|
12654
12846
|
if (!currentIndex) {
|
|
12655
12847
|
setPaneEmpty(mappingBody, t2.es.selectIndex);
|
|
12656
|
-
} else if (lastMapping && mappingBody.querySelector(".
|
|
12848
|
+
} else if (lastMapping && mappingBody.querySelector(".db-detail-table")) {
|
|
12657
12849
|
renderMapping(lastMapping);
|
|
12658
12850
|
}
|
|
12659
12851
|
if (!activeDocRow) {
|
|
@@ -15998,7 +16190,8 @@ ${frontmatter.yaml}
|
|
|
15998
16190
|
header.className = "db-schema-header";
|
|
15999
16191
|
const headerTitle = document.createElement("span");
|
|
16000
16192
|
headerTitle.className = "db-schema-header-title";
|
|
16001
|
-
|
|
16193
|
+
const tableComment = extra?.tableComment?.trim();
|
|
16194
|
+
headerTitle.textContent = tableComment ? `Schema: ${table2} — ${tableComment}` : `Schema: ${table2}`;
|
|
16002
16195
|
header.appendChild(headerTitle);
|
|
16003
16196
|
if (deps.onRefresh) {
|
|
16004
16197
|
refreshBtn = document.createElement("button");
|
|
@@ -19716,7 +19909,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19716
19909
|
container.appendChild(colRow);
|
|
19717
19910
|
}
|
|
19718
19911
|
}
|
|
19719
|
-
function toggleExpand(tableName, _node, arrow, children) {
|
|
19912
|
+
function toggleExpand(tableName, _node, arrow, children, columnsHost) {
|
|
19720
19913
|
const expanded = expandedTables.has(tableName);
|
|
19721
19914
|
if (expanded) {
|
|
19722
19915
|
expandedTables.delete(tableName);
|
|
@@ -19728,8 +19921,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19728
19921
|
children.hidden = false;
|
|
19729
19922
|
arrow.classList.add("expanded");
|
|
19730
19923
|
callbacks.onExpandedTableChange?.(tableName, true);
|
|
19731
|
-
if (
|
|
19732
|
-
renderColumns(
|
|
19924
|
+
if (columnsHost.children.length === 0) {
|
|
19925
|
+
renderColumns(columnsHost, tableName);
|
|
19733
19926
|
}
|
|
19734
19927
|
}
|
|
19735
19928
|
}
|
|
@@ -19805,12 +19998,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19805
19998
|
const children = document.createElement("div");
|
|
19806
19999
|
children.className = "db-table-children";
|
|
19807
20000
|
children.hidden = !expandedTables.has(table2.name);
|
|
20001
|
+
const comment2 = columnCommentText(table2.comment);
|
|
20002
|
+
if (comment2) {
|
|
20003
|
+
const tableComment = document.createElement("div");
|
|
20004
|
+
tableComment.className = "db-table-col-comment";
|
|
20005
|
+
tableComment.textContent = comment2;
|
|
20006
|
+
tableComment.title = comment2;
|
|
20007
|
+
children.appendChild(tableComment);
|
|
20008
|
+
}
|
|
20009
|
+
const columnsHost = document.createElement("div");
|
|
20010
|
+
children.appendChild(columnsHost);
|
|
19808
20011
|
if (expandedTables.has(table2.name)) {
|
|
19809
|
-
renderColumns(
|
|
20012
|
+
renderColumns(columnsHost, table2.name);
|
|
19810
20013
|
}
|
|
19811
20014
|
arrow.addEventListener("click", (e2) => {
|
|
19812
20015
|
e2.stopPropagation();
|
|
19813
|
-
toggleExpand(table2.name, node, arrow, children);
|
|
20016
|
+
toggleExpand(table2.name, node, arrow, children, columnsHost);
|
|
19814
20017
|
});
|
|
19815
20018
|
row.addEventListener("click", () => callbacks.onSelectTable(table2.name));
|
|
19816
20019
|
row.addEventListener("contextmenu", (e2) => showContextMenu(e2, table2.name));
|
|
@@ -21116,7 +21319,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21116
21319
|
if (generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
21117
21320
|
return;
|
|
21118
21321
|
}
|
|
21119
|
-
schemaView.render(table2, columns, schemaCache?.indexes || []
|
|
21322
|
+
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21323
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21324
|
+
});
|
|
21120
21325
|
}
|
|
21121
21326
|
}
|
|
21122
21327
|
async function selectTableSchemaOnly(table2, generation = loadGeneration) {
|
|
@@ -21132,7 +21337,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21132
21337
|
if (generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
21133
21338
|
return;
|
|
21134
21339
|
}
|
|
21135
|
-
schemaView.render(table2, columns, schemaCache?.indexes || []
|
|
21340
|
+
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21341
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21342
|
+
});
|
|
21136
21343
|
}
|
|
21137
21344
|
async function fetchColumns(table2) {
|
|
21138
21345
|
if (schemaCache?.columnsMap?.[table2]) {
|
|
@@ -21158,7 +21365,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21158
21365
|
if (!currentDbInfo)
|
|
21159
21366
|
return;
|
|
21160
21367
|
const columns = await fetchColumns(table2);
|
|
21161
|
-
schemaView.render(table2, columns, schemaCache?.indexes || []
|
|
21368
|
+
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21369
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21370
|
+
});
|
|
21162
21371
|
}
|
|
21163
21372
|
async function refreshCurrentSchemaView() {
|
|
21164
21373
|
if (!currentDbInfo || !currentTable)
|
|
@@ -21190,7 +21399,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21190
21399
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId || currentTable !== nextTable) {
|
|
21191
21400
|
return;
|
|
21192
21401
|
}
|
|
21193
|
-
schemaView.render(nextTable, columns, schema.indexes || []
|
|
21402
|
+
schemaView.render(nextTable, columns, schema.indexes || [], {
|
|
21403
|
+
tableComment: schema.tables.find((entry) => entry.name === nextTable)?.comment
|
|
21404
|
+
});
|
|
21194
21405
|
cb.onStateChange();
|
|
21195
21406
|
} catch (err) {
|
|
21196
21407
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId || isAbortError2(err)) {
|
|
@@ -21217,7 +21428,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21217
21428
|
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21218
21429
|
foreignKeys: schemaCache?.foreignKeys,
|
|
21219
21430
|
triggers: data.triggers,
|
|
21220
|
-
ddl: data.sql
|
|
21431
|
+
ddl: data.sql,
|
|
21432
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21221
21433
|
});
|
|
21222
21434
|
} catch {}
|
|
21223
21435
|
}
|
|
@@ -21410,11 +21622,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21410
21622
|
dbSelect.value = target;
|
|
21411
21623
|
currentDbInfo = files.find((f2) => f2.id === target) || null;
|
|
21412
21624
|
syncConnectionActions();
|
|
21625
|
+
const forceReload = options.forceReload ?? false;
|
|
21413
21626
|
const explorerInitial = {
|
|
21414
|
-
redis: pendingRedisInitial,
|
|
21415
|
-
es: pendingEsInitial,
|
|
21416
|
-
s3: pendingS3Initial,
|
|
21417
|
-
dynamodb: pendingDynamodbInitial
|
|
21627
|
+
redis: pendingRedisInitial ?? (forceReload && currentDbInfo?.kind === "redis" ? redisExplorer.getSelection() : undefined),
|
|
21628
|
+
es: pendingEsInitial ?? (forceReload && currentDbInfo?.kind === "elasticsearch" ? esExplorer.getSelection() : undefined),
|
|
21629
|
+
s3: pendingS3Initial ?? (forceReload && currentDbInfo?.kind === "s3" ? s3Explorer.getSelection() : undefined),
|
|
21630
|
+
dynamodb: pendingDynamodbInitial ?? (forceReload && currentDbInfo?.kind === "dynamodb" ? dynamodbExplorer.getSelection() : undefined)
|
|
21418
21631
|
};
|
|
21419
21632
|
pendingRedisInitial = undefined;
|
|
21420
21633
|
pendingEsInitial = undefined;
|
|
@@ -21547,7 +21760,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21547
21760
|
}
|
|
21548
21761
|
} else if (currentDbInfo?.kind === "dynamodb") {
|
|
21549
21762
|
const sel = dynamodbExplorer.getSelection();
|
|
21550
|
-
if (sel.table !== undefined || sel.mode !== "scan" || sel.keyConditionExpression !== undefined || sel.filterExpression !== undefined || sel.expressionAttributeValues !== undefined || sel.itemKey !== undefined) {
|
|
21763
|
+
if (sel.table !== undefined || sel.mode !== "scan" || sel.keyConditionExpression !== undefined || sel.filterExpression !== undefined || sel.expressionAttributeValues !== undefined || sel.itemKey !== undefined || sel.detailTab === "item") {
|
|
21551
21764
|
state.dynamodb = sel;
|
|
21552
21765
|
}
|
|
21553
21766
|
}
|
|
@@ -22612,7 +22825,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22612
22825
|
if (!mounted || activeTabId !== id)
|
|
22613
22826
|
return;
|
|
22614
22827
|
const state = entry.pane.getState();
|
|
22615
|
-
await entry.pane.enter(state.dbId ?? undefined, state.schema ?? undefined, state.table ?? undefined, state.view, { autoSelectFirst: state.dbId !== null });
|
|
22828
|
+
await entry.pane.enter(state.dbId ?? undefined, state.schema ?? undefined, state.table ?? undefined, state.view, { autoSelectFirst: state.dbId !== null, forceReload: true });
|
|
22616
22829
|
if (!mounted || activeTabId !== id)
|
|
22617
22830
|
return;
|
|
22618
22831
|
refreshChipLabel(id);
|
|
@@ -26108,7 +26321,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26108
26321
|
],
|
|
26109
26322
|
[
|
|
26110
26323
|
"DynamoDB / LocalStack",
|
|
26111
|
-
"Detected when DynamoDB is enabled on a LocalStack compose service. List tables,
|
|
26324
|
+
"Detected when DynamoDB is enabled on a LocalStack compose service. List tables, browse a Structure tab (key schema, GSI/LSI, and non-key attribute types inferred from loaded items), scan or query items, follow pagination tokens, and open item details with a copyable key. Browsing is read-only."
|
|
26112
26325
|
],
|
|
26113
26326
|
[
|
|
26114
26327
|
"S3 / MinIO / LocalStack",
|
|
@@ -26130,7 +26343,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26130
26343
|
],
|
|
26131
26344
|
[
|
|
26132
26345
|
"Sidebar",
|
|
26133
|
-
"DB selector, PostgreSQL schema selector, table tree (expand to see columns), filter, Rails FK inference toggle, and icon toolbar for Query / ER / Search / Snapshot tabs."
|
|
26346
|
+
"DB selector, PostgreSQL schema selector, table tree (expand to see columns and a table description when available), filter, Rails FK inference toggle, and icon toolbar for Query / ER / Search / Snapshot tabs."
|
|
26134
26347
|
],
|
|
26135
26348
|
[
|
|
26136
26349
|
"Data tab",
|
|
@@ -26142,7 +26355,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26142
26355
|
],
|
|
26143
26356
|
[
|
|
26144
26357
|
"Schema tab",
|
|
26145
|
-
"
|
|
26358
|
+
"Table description when available, column definitions, indexes, foreign keys, triggers, and DDL, with an in-tab refresh action for reloading the current table structure."
|
|
26146
26359
|
],
|
|
26147
26360
|
[
|
|
26148
26361
|
"Query editor",
|
|
@@ -26775,7 +26988,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26775
26988
|
],
|
|
26776
26989
|
[
|
|
26777
26990
|
"DynamoDB / LocalStack",
|
|
26778
|
-
"LocalStack の compose サービスで DynamoDB
|
|
26991
|
+
"LocalStack の compose サービスで DynamoDB が有効な場合に検出。テーブル一覧、構造タブ(キースキーマ・GSI/LSI・読み込み済みアイテムから推測した非キー属性の型)、scan / query、継続トークンによるページング、コピー可能なキー付きのアイテム詳細を表示します。閲覧専用です。"
|
|
26779
26992
|
],
|
|
26780
26993
|
[
|
|
26781
26994
|
"S3 / MinIO / LocalStack",
|
|
@@ -26797,7 +27010,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26797
27010
|
],
|
|
26798
27011
|
[
|
|
26799
27012
|
"サイドバー",
|
|
26800
|
-
"DB 選択、PostgreSQL
|
|
27013
|
+
"DB 選択、PostgreSQL スキーマセレクター、テーブルツリー(展開でカラムと、あればテーブルコメントを表示)、フィルター、Rails 命名規約による仮想 FK 推測トグル、Query / ER / Search / Snapshot アイコンツールバー。"
|
|
26801
27014
|
],
|
|
26802
27015
|
[
|
|
26803
27016
|
"Data タブ",
|
|
@@ -26809,7 +27022,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26809
27022
|
],
|
|
26810
27023
|
[
|
|
26811
27024
|
"Schema タブ",
|
|
26812
|
-
"
|
|
27025
|
+
"テーブルコメント(あれば)、カラム定義、インデックス、外部キー、トリガー、DDL。現在の表構造だけを再読み込みするタブ内更新にも対応。"
|
|
26813
27026
|
],
|
|
26814
27027
|
[
|
|
26815
27028
|
"クエリエディター",
|
|
@@ -36152,9 +36365,9 @@ code-viewer query agent-help`
|
|
|
36152
36365
|
displaySource: "Applies to all projects in this browser.",
|
|
36153
36366
|
excludedDirectories: "Excluded directories",
|
|
36154
36367
|
omitDirs: "Skip these directory names while browsing and searching",
|
|
36155
|
-
omitDirsHelp: "Reads no contents inside these directories. Applies to the sidebar (Files), Ctrl+K (file search), Ctrl+G (grep), Datastores, and the file change watcher.",
|
|
36368
|
+
omitDirsHelp: "Reads no contents inside these directories. Applies to the sidebar (Files), Ctrl+K (file search), Ctrl+G (grep), Datastores, and the file change watcher. Supports gitignore-style wildcards (*, ?, [abc], [!abc]).",
|
|
36156
36369
|
excludeNames: "Hide these file or directory names completely",
|
|
36157
|
-
excludeNamesHelp: "Removes matching files or directories from the sidebar, search, and grep results entirely. Unlike Skip, the names themselves disappear from the UI.",
|
|
36370
|
+
excludeNamesHelp: "Removes matching files or directories from the sidebar, search, and grep results entirely. Unlike Skip, the names themselves disappear from the UI. Supports gitignore-style wildcards (*, ?, [abc], [!abc]).",
|
|
36158
36371
|
reset: "Restore defaults",
|
|
36159
36372
|
autosaveNote: "Changes save automatically.",
|
|
36160
36373
|
scopeSource: (project, source) => `Saved for project "${project}" in this browser. Source: ${source}. Used by the sidebar, Ctrl+K, Ctrl+G, Datastores, and the file change watcher. Restore defaults removes the browser override.`,
|
|
@@ -36442,9 +36655,9 @@ code-viewer query agent-help`
|
|
|
36442
36655
|
displaySource: "このブラウザのすべてのプロジェクトに適用されます。",
|
|
36443
36656
|
excludedDirectories: "除外ディレクトリ",
|
|
36444
36657
|
omitDirs: "閲覧と検索でスキップするディレクトリ名",
|
|
36445
|
-
omitDirsHelp: "これらのディレクトリの中身は読み込みません。サイドバー(Files)・Ctrl+K(ファイル検索)・Ctrl+G(grep)・Datastores・File change watcher の5機能すべてに適用されます。",
|
|
36658
|
+
omitDirsHelp: "これらのディレクトリの中身は読み込みません。サイドバー(Files)・Ctrl+K(ファイル検索)・Ctrl+G(grep)・Datastores・File change watcher の5機能すべてに適用されます。gitignore方式のワイルドカード(*, ?, [abc], [!abc])に対応しています。",
|
|
36446
36659
|
excludeNames: "完全に非表示にするファイル名またはディレクトリ名",
|
|
36447
|
-
excludeNamesHelp: "リスト中の名前に一致するファイル/ディレクトリを、サイドバー・検索結果・grep 結果から完全に消します。Skip と違い、名前自体が UI に出なくなります。",
|
|
36660
|
+
excludeNamesHelp: "リスト中の名前に一致するファイル/ディレクトリを、サイドバー・検索結果・grep 結果から完全に消します。Skip と違い、名前自体が UI に出なくなります。gitignore方式のワイルドカード(*, ?, [abc], [!abc])に対応しています。",
|
|
36448
36661
|
reset: "デフォルトに戻す",
|
|
36449
36662
|
autosaveNote: "変更は自動で保存されます。",
|
|
36450
36663
|
scopeSource: (project, source) => `このブラウザのプロジェクト "${project}" に保存されます。ソース: ${source}。サイドバー、Ctrl+K、Ctrl+G、Datastores、File change watcher で使われます。「デフォルトに戻す」でブラウザ側の上書きを削除します。`,
|
package/web/style.css
CHANGED
|
@@ -11654,20 +11654,23 @@ body.db-resizing {
|
|
|
11654
11654
|
cursor: default;
|
|
11655
11655
|
color: var(--fg-muted);
|
|
11656
11656
|
}
|
|
11657
|
-
.
|
|
11657
|
+
/* .db-detail-* は es-explorer (mapping/doc) と dynamodb-explorer
|
|
11658
|
+
(structure/item) が共有する詳細ペイン/タブ切替/テーブルの共通スタイル。
|
|
11659
|
+
detail-tabs.ts / detail-table.ts のクラス名と対応する。 */
|
|
11660
|
+
.db-detail-pane {
|
|
11658
11661
|
flex: 1;
|
|
11659
11662
|
display: flex;
|
|
11660
11663
|
flex-direction: column;
|
|
11661
11664
|
overflow: hidden;
|
|
11662
11665
|
background: var(--bg);
|
|
11663
11666
|
}
|
|
11664
|
-
.
|
|
11667
|
+
.db-detail-tabs {
|
|
11665
11668
|
display: flex;
|
|
11666
11669
|
border-bottom: 1px solid var(--border);
|
|
11667
11670
|
background: var(--bg-soft);
|
|
11668
11671
|
flex-shrink: 0;
|
|
11669
11672
|
}
|
|
11670
|
-
.
|
|
11673
|
+
.db-detail-tab {
|
|
11671
11674
|
padding: 6px 14px;
|
|
11672
11675
|
border: none;
|
|
11673
11676
|
background: transparent;
|
|
@@ -11676,12 +11679,11 @@ body.db-resizing {
|
|
|
11676
11679
|
color: var(--fg-muted);
|
|
11677
11680
|
border-bottom: 2px solid transparent;
|
|
11678
11681
|
}
|
|
11679
|
-
.
|
|
11682
|
+
.db-detail-tab.active {
|
|
11680
11683
|
color: var(--fg);
|
|
11681
11684
|
border-bottom-color: var(--accent);
|
|
11682
11685
|
}
|
|
11683
|
-
.
|
|
11684
|
-
.es-doc-body {
|
|
11686
|
+
.db-detail-tab-body {
|
|
11685
11687
|
flex: 1;
|
|
11686
11688
|
overflow: auto;
|
|
11687
11689
|
padding: 12px;
|
|
@@ -11753,26 +11755,27 @@ body.db-resizing {
|
|
|
11753
11755
|
color: var(--fg-muted);
|
|
11754
11756
|
margin-bottom: 8px;
|
|
11755
11757
|
}
|
|
11756
|
-
.
|
|
11758
|
+
.db-detail-table {
|
|
11757
11759
|
width: 100%;
|
|
11758
11760
|
border-collapse: collapse;
|
|
11759
11761
|
font-family: var(--mono, monospace);
|
|
11760
11762
|
font-size: var(--db-font-mono);
|
|
11763
|
+
margin-bottom: 16px;
|
|
11761
11764
|
}
|
|
11762
|
-
.
|
|
11763
|
-
.
|
|
11765
|
+
.db-detail-table th,
|
|
11766
|
+
.db-detail-table td {
|
|
11764
11767
|
text-align: left;
|
|
11765
11768
|
padding: 4px 8px;
|
|
11766
11769
|
border-bottom: 1px solid var(--border);
|
|
11767
11770
|
}
|
|
11768
|
-
.
|
|
11771
|
+
.db-detail-table th {
|
|
11769
11772
|
font-weight: 600;
|
|
11770
11773
|
background: var(--bg-soft);
|
|
11771
11774
|
}
|
|
11772
|
-
.
|
|
11775
|
+
.db-detail-table-primary {
|
|
11773
11776
|
font-weight: 500;
|
|
11774
11777
|
}
|
|
11775
|
-
.
|
|
11778
|
+
.db-detail-table-muted {
|
|
11776
11779
|
color: var(--fg-muted);
|
|
11777
11780
|
}
|
|
11778
11781
|
.es-doc-source {
|
|
@@ -11782,7 +11785,7 @@ body.db-resizing {
|
|
|
11782
11785
|
word-break: break-all;
|
|
11783
11786
|
margin: 0;
|
|
11784
11787
|
}
|
|
11785
|
-
.
|
|
11788
|
+
.db-value-empty {
|
|
11786
11789
|
color: var(--fg-muted);
|
|
11787
11790
|
font-size: var(--db-font-base);
|
|
11788
11791
|
font-style: italic;
|
|
@@ -12472,13 +12475,6 @@ body.db-resizing {
|
|
|
12472
12475
|
cursor: default;
|
|
12473
12476
|
color: var(--fg-muted);
|
|
12474
12477
|
}
|
|
12475
|
-
.dynamodb-detail-pane {
|
|
12476
|
-
flex: 1;
|
|
12477
|
-
overflow: auto;
|
|
12478
|
-
padding: 12px;
|
|
12479
|
-
color: var(--fg);
|
|
12480
|
-
background: var(--bg);
|
|
12481
|
-
}
|
|
12482
12478
|
.dynamodb-table-info-header,
|
|
12483
12479
|
.dynamodb-item-detail-header {
|
|
12484
12480
|
display: flex;
|
|
@@ -12491,8 +12487,7 @@ body.db-resizing {
|
|
|
12491
12487
|
border-bottom: 1px solid var(--border);
|
|
12492
12488
|
margin-bottom: 8px;
|
|
12493
12489
|
}
|
|
12494
|
-
.dynamodb-table-info-meta
|
|
12495
|
-
.dynamodb-table-info-keys {
|
|
12490
|
+
.dynamodb-table-info-meta {
|
|
12496
12491
|
font-family: var(--mono, monospace);
|
|
12497
12492
|
font-size: var(--db-font-sm);
|
|
12498
12493
|
color: var(--fg-muted);
|
|
@@ -12504,8 +12499,35 @@ body.db-resizing {
|
|
|
12504
12499
|
font-size: var(--db-font-sm);
|
|
12505
12500
|
font-weight: 400;
|
|
12506
12501
|
}
|
|
12507
|
-
.dynamodb-
|
|
12508
|
-
margin-
|
|
12502
|
+
.dynamodb-index-section {
|
|
12503
|
+
margin-bottom: 16px;
|
|
12504
|
+
}
|
|
12505
|
+
.dynamodb-index-heading {
|
|
12506
|
+
font-weight: 600;
|
|
12507
|
+
font-size: var(--db-font-sm);
|
|
12508
|
+
color: var(--fg-muted);
|
|
12509
|
+
margin-bottom: 4px;
|
|
12510
|
+
}
|
|
12511
|
+
.dynamodb-index-row {
|
|
12512
|
+
padding: 4px 0;
|
|
12513
|
+
border-bottom: 1px solid var(--border);
|
|
12514
|
+
}
|
|
12515
|
+
.dynamodb-index-name {
|
|
12516
|
+
font-family: var(--mono, monospace);
|
|
12517
|
+
font-size: var(--db-font-mono);
|
|
12518
|
+
font-weight: 500;
|
|
12519
|
+
}
|
|
12520
|
+
.dynamodb-index-detail {
|
|
12521
|
+
font-family: var(--mono, monospace);
|
|
12522
|
+
font-size: var(--db-font-sm);
|
|
12523
|
+
color: var(--fg-muted);
|
|
12524
|
+
}
|
|
12525
|
+
.dynamodb-attr-note {
|
|
12526
|
+
margin-top: -4px;
|
|
12527
|
+
margin-bottom: 16px;
|
|
12528
|
+
font-size: var(--db-font-sm);
|
|
12529
|
+
color: var(--fg-muted);
|
|
12530
|
+
font-style: italic;
|
|
12509
12531
|
}
|
|
12510
12532
|
.dynamodb-item-source {
|
|
12511
12533
|
font-family: var(--mono, monospace);
|