@youtyan/code-viewer 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/code-viewer.js +18 -17
- package/package.json +1 -1
- package/web/app.js +35 -15
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.
|
package/dist/code-viewer.js
CHANGED
|
@@ -10882,7 +10882,7 @@ function buildExecInvocation(config, sql) {
|
|
|
10882
10882
|
"-t",
|
|
10883
10883
|
"-A",
|
|
10884
10884
|
"-F",
|
|
10885
|
-
|
|
10885
|
+
PG_FIELD_SEPARATOR,
|
|
10886
10886
|
"-R",
|
|
10887
10887
|
PG_RECORD_SEPARATOR,
|
|
10888
10888
|
"-v",
|
|
@@ -11226,14 +11226,14 @@ function decodeMysqlBatchField(value) {
|
|
|
11226
11226
|
}
|
|
11227
11227
|
return out;
|
|
11228
11228
|
}
|
|
11229
|
-
function splitTsvLine(line, decodeFields) {
|
|
11230
|
-
const fields = line.split(
|
|
11229
|
+
function splitTsvLine(line, decodeFields, fieldSeparator = "\t") {
|
|
11230
|
+
const fields = line.split(fieldSeparator);
|
|
11231
11231
|
return decodeFields ? fields.map(decodeMysqlBatchField) : fields;
|
|
11232
11232
|
}
|
|
11233
11233
|
function stripFinalRecordSeparator(text, recordSeparator) {
|
|
11234
11234
|
return text.endsWith(recordSeparator) ? text.slice(0, -recordSeparator.length) : text;
|
|
11235
11235
|
}
|
|
11236
|
-
function parseTsvOutput(stdout, hasHeader, recordSeparator) {
|
|
11236
|
+
function parseTsvOutput(stdout, hasHeader, recordSeparator, fieldSeparator) {
|
|
11237
11237
|
const text = recordSeparator ? stripFinalRecordSeparator(stripFinalLineBreak(stdout), recordSeparator) : stripFinalLineBreak(stdout);
|
|
11238
11238
|
if (text.length === 0)
|
|
11239
11239
|
return { columns: [], rows: [] };
|
|
@@ -11241,11 +11241,11 @@ function parseTsvOutput(stdout, hasHeader, recordSeparator) {
|
|
|
11241
11241
|
if (lines.length === 0)
|
|
11242
11242
|
return { columns: [], rows: [] };
|
|
11243
11243
|
if (hasHeader) {
|
|
11244
|
-
const columns = splitTsvLine(lines[0], true);
|
|
11245
|
-
const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true));
|
|
11244
|
+
const columns = splitTsvLine(lines[0], true, fieldSeparator);
|
|
11245
|
+
const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true, fieldSeparator));
|
|
11246
11246
|
return { columns, rows: rows2 };
|
|
11247
11247
|
}
|
|
11248
|
-
const rows = lines.map((line) => splitTsvLine(line, false));
|
|
11248
|
+
const rows = lines.map((line) => splitTsvLine(line, false, fieldSeparator));
|
|
11249
11249
|
return { columns: [], rows };
|
|
11250
11250
|
}
|
|
11251
11251
|
function isMysqlSpatialType(type) {
|
|
@@ -11315,7 +11315,7 @@ function createSqlCliAdapter(config) {
|
|
|
11315
11315
|
if (result.code !== 0) {
|
|
11316
11316
|
throw new Error(result.stderr.trim() || "query failed");
|
|
11317
11317
|
}
|
|
11318
|
-
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
11318
|
+
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, config.kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
|
|
11319
11319
|
}
|
|
11320
11320
|
function toDbValue(val) {
|
|
11321
11321
|
if (val === "NULL" || val === "\\N")
|
|
@@ -11392,15 +11392,16 @@ function createSqlCliAdapter(config) {
|
|
|
11392
11392
|
async getTablesAsync(signal) {
|
|
11393
11393
|
let sql;
|
|
11394
11394
|
if (config.kind === "postgresql") {
|
|
11395
|
-
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ${postgresSchemaLiteral()} ORDER BY table_name`;
|
|
11395
|
+
sql = `SELECT t.table_name, t.table_type, COALESCE(obj_description(cls.oid, 'pg_class'), '') FROM information_schema.tables t JOIN pg_namespace n ON n.nspname = t.table_schema JOIN pg_class cls ON cls.relnamespace = n.oid AND cls.relname = t.table_name WHERE t.table_schema = ${postgresSchemaLiteral()} ORDER BY t.table_name`;
|
|
11396
11396
|
} else {
|
|
11397
|
-
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
|
|
11397
|
+
sql = `SELECT table_name, table_type, table_comment FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
|
|
11398
11398
|
}
|
|
11399
11399
|
const result = await execAsync(sql, signal);
|
|
11400
11400
|
return result.rows.map((row) => ({
|
|
11401
11401
|
name: row[0],
|
|
11402
11402
|
type: row[1] === "VIEW" ? "view" : "table",
|
|
11403
|
-
rowCount: null
|
|
11403
|
+
rowCount: null,
|
|
11404
|
+
comment: row[2] || null
|
|
11404
11405
|
}));
|
|
11405
11406
|
},
|
|
11406
11407
|
async getColumnsAsync(table, signal) {
|
|
@@ -11413,11 +11414,11 @@ function createSqlCliAdapter(config) {
|
|
|
11413
11414
|
},
|
|
11414
11415
|
async getIndexesAsync(signal) {
|
|
11415
11416
|
let sql;
|
|
11416
|
-
const INDEX_COL_SEP = "\
|
|
11417
|
+
const INDEX_COL_SEP = "\x1D";
|
|
11417
11418
|
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'\\
|
|
11419
|
+
sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\x1d' ORDER BY k.ord), '') FROM pg_index ix JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = ${postgresSchemaLiteral()} AND i.relname NOT LIKE 'pg_%' GROUP BY i.relname, t.relname, ix.indisunique ORDER BY t.relname, i.relname`;
|
|
11419
11420
|
} 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 '\
|
|
11421
|
+
sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\x1D') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
|
|
11421
11422
|
}
|
|
11422
11423
|
const result = await execAsync(sql, signal);
|
|
11423
11424
|
return result.rows.map((row) => ({
|
|
@@ -11818,7 +11819,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
11818
11819
|
return fallback;
|
|
11819
11820
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
11820
11821
|
}
|
|
11821
|
-
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
11822
|
+
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
|
|
11822
11823
|
const dbs = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
11823
11824
|
const value = dbs.length > 0 ? dbs : fallbackDockerDatabases(defaultDb);
|
|
11824
11825
|
return setDockerDatabasesCache(cacheKey, value, value.length > 0 ? DOCKER_DATABASES_POSITIVE_TTL_MS : DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
@@ -11858,7 +11859,7 @@ async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, sign
|
|
|
11858
11859
|
if (result.code !== 0) {
|
|
11859
11860
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
11860
11861
|
}
|
|
11861
|
-
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR);
|
|
11862
|
+
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR, PG_FIELD_SEPARATOR);
|
|
11862
11863
|
const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
11863
11864
|
const value = schemas.length > 0 ? schemas : ["public"];
|
|
11864
11865
|
return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
|
|
@@ -11916,7 +11917,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
11916
11917
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
11917
11918
|
});
|
|
11918
11919
|
}
|
|
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";
|
|
11920
|
+
var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config), COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", PG_FIELD_SEPARATOR = "\x1F", MYSQL_SPATIAL_TYPES, createDockerAdapter, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
|
|
11920
11921
|
var init_docker = __esm(() => {
|
|
11921
11922
|
init_mutate();
|
|
11922
11923
|
init_sql_snapshot();
|
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -15998,7 +15998,8 @@ ${frontmatter.yaml}
|
|
|
15998
15998
|
header.className = "db-schema-header";
|
|
15999
15999
|
const headerTitle = document.createElement("span");
|
|
16000
16000
|
headerTitle.className = "db-schema-header-title";
|
|
16001
|
-
|
|
16001
|
+
const tableComment = extra?.tableComment?.trim();
|
|
16002
|
+
headerTitle.textContent = tableComment ? `Schema: ${table2} — ${tableComment}` : `Schema: ${table2}`;
|
|
16002
16003
|
header.appendChild(headerTitle);
|
|
16003
16004
|
if (deps.onRefresh) {
|
|
16004
16005
|
refreshBtn = document.createElement("button");
|
|
@@ -19716,7 +19717,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19716
19717
|
container.appendChild(colRow);
|
|
19717
19718
|
}
|
|
19718
19719
|
}
|
|
19719
|
-
function toggleExpand(tableName, _node, arrow, children) {
|
|
19720
|
+
function toggleExpand(tableName, _node, arrow, children, columnsHost) {
|
|
19720
19721
|
const expanded = expandedTables.has(tableName);
|
|
19721
19722
|
if (expanded) {
|
|
19722
19723
|
expandedTables.delete(tableName);
|
|
@@ -19728,8 +19729,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19728
19729
|
children.hidden = false;
|
|
19729
19730
|
arrow.classList.add("expanded");
|
|
19730
19731
|
callbacks.onExpandedTableChange?.(tableName, true);
|
|
19731
|
-
if (
|
|
19732
|
-
renderColumns(
|
|
19732
|
+
if (columnsHost.children.length === 0) {
|
|
19733
|
+
renderColumns(columnsHost, tableName);
|
|
19733
19734
|
}
|
|
19734
19735
|
}
|
|
19735
19736
|
}
|
|
@@ -19805,12 +19806,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19805
19806
|
const children = document.createElement("div");
|
|
19806
19807
|
children.className = "db-table-children";
|
|
19807
19808
|
children.hidden = !expandedTables.has(table2.name);
|
|
19809
|
+
const comment2 = columnCommentText(table2.comment);
|
|
19810
|
+
if (comment2) {
|
|
19811
|
+
const tableComment = document.createElement("div");
|
|
19812
|
+
tableComment.className = "db-table-col-comment";
|
|
19813
|
+
tableComment.textContent = comment2;
|
|
19814
|
+
tableComment.title = comment2;
|
|
19815
|
+
children.appendChild(tableComment);
|
|
19816
|
+
}
|
|
19817
|
+
const columnsHost = document.createElement("div");
|
|
19818
|
+
children.appendChild(columnsHost);
|
|
19808
19819
|
if (expandedTables.has(table2.name)) {
|
|
19809
|
-
renderColumns(
|
|
19820
|
+
renderColumns(columnsHost, table2.name);
|
|
19810
19821
|
}
|
|
19811
19822
|
arrow.addEventListener("click", (e2) => {
|
|
19812
19823
|
e2.stopPropagation();
|
|
19813
|
-
toggleExpand(table2.name, node, arrow, children);
|
|
19824
|
+
toggleExpand(table2.name, node, arrow, children, columnsHost);
|
|
19814
19825
|
});
|
|
19815
19826
|
row.addEventListener("click", () => callbacks.onSelectTable(table2.name));
|
|
19816
19827
|
row.addEventListener("contextmenu", (e2) => showContextMenu(e2, table2.name));
|
|
@@ -21116,7 +21127,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21116
21127
|
if (generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
21117
21128
|
return;
|
|
21118
21129
|
}
|
|
21119
|
-
schemaView.render(table2, columns, schemaCache?.indexes || []
|
|
21130
|
+
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21131
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21132
|
+
});
|
|
21120
21133
|
}
|
|
21121
21134
|
}
|
|
21122
21135
|
async function selectTableSchemaOnly(table2, generation = loadGeneration) {
|
|
@@ -21132,7 +21145,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21132
21145
|
if (generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
21133
21146
|
return;
|
|
21134
21147
|
}
|
|
21135
|
-
schemaView.render(table2, columns, schemaCache?.indexes || []
|
|
21148
|
+
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21149
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21150
|
+
});
|
|
21136
21151
|
}
|
|
21137
21152
|
async function fetchColumns(table2) {
|
|
21138
21153
|
if (schemaCache?.columnsMap?.[table2]) {
|
|
@@ -21158,7 +21173,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21158
21173
|
if (!currentDbInfo)
|
|
21159
21174
|
return;
|
|
21160
21175
|
const columns = await fetchColumns(table2);
|
|
21161
|
-
schemaView.render(table2, columns, schemaCache?.indexes || []
|
|
21176
|
+
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21177
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21178
|
+
});
|
|
21162
21179
|
}
|
|
21163
21180
|
async function refreshCurrentSchemaView() {
|
|
21164
21181
|
if (!currentDbInfo || !currentTable)
|
|
@@ -21190,7 +21207,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21190
21207
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId || currentTable !== nextTable) {
|
|
21191
21208
|
return;
|
|
21192
21209
|
}
|
|
21193
|
-
schemaView.render(nextTable, columns, schema.indexes || []
|
|
21210
|
+
schemaView.render(nextTable, columns, schema.indexes || [], {
|
|
21211
|
+
tableComment: schema.tables.find((entry) => entry.name === nextTable)?.comment
|
|
21212
|
+
});
|
|
21194
21213
|
cb.onStateChange();
|
|
21195
21214
|
} catch (err) {
|
|
21196
21215
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId || isAbortError2(err)) {
|
|
@@ -21217,7 +21236,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21217
21236
|
schemaView.render(table2, columns, schemaCache?.indexes || [], {
|
|
21218
21237
|
foreignKeys: schemaCache?.foreignKeys,
|
|
21219
21238
|
triggers: data.triggers,
|
|
21220
|
-
ddl: data.sql
|
|
21239
|
+
ddl: data.sql,
|
|
21240
|
+
tableComment: schemaCache?.tables.find((entry) => entry.name === table2)?.comment
|
|
21221
21241
|
});
|
|
21222
21242
|
} catch {}
|
|
21223
21243
|
}
|
|
@@ -26130,7 +26150,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26130
26150
|
],
|
|
26131
26151
|
[
|
|
26132
26152
|
"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."
|
|
26153
|
+
"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
26154
|
],
|
|
26135
26155
|
[
|
|
26136
26156
|
"Data tab",
|
|
@@ -26142,7 +26162,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26142
26162
|
],
|
|
26143
26163
|
[
|
|
26144
26164
|
"Schema tab",
|
|
26145
|
-
"
|
|
26165
|
+
"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
26166
|
],
|
|
26147
26167
|
[
|
|
26148
26168
|
"Query editor",
|
|
@@ -26797,7 +26817,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26797
26817
|
],
|
|
26798
26818
|
[
|
|
26799
26819
|
"サイドバー",
|
|
26800
|
-
"DB 選択、PostgreSQL
|
|
26820
|
+
"DB 選択、PostgreSQL スキーマセレクター、テーブルツリー(展開でカラムと、あればテーブルコメントを表示)、フィルター、Rails 命名規約による仮想 FK 推測トグル、Query / ER / Search / Snapshot アイコンツールバー。"
|
|
26801
26821
|
],
|
|
26802
26822
|
[
|
|
26803
26823
|
"Data タブ",
|
|
@@ -26809,7 +26829,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26809
26829
|
],
|
|
26810
26830
|
[
|
|
26811
26831
|
"Schema タブ",
|
|
26812
|
-
"
|
|
26832
|
+
"テーブルコメント(あれば)、カラム定義、インデックス、外部キー、トリガー、DDL。現在の表構造だけを再読み込みするタブ内更新にも対応。"
|
|
26813
26833
|
],
|
|
26814
26834
|
[
|
|
26815
26835
|
"クエリエディター",
|