@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 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.
@@ -10882,7 +10882,7 @@ function buildExecInvocation(config, sql) {
10882
10882
  "-t",
10883
10883
  "-A",
10884
10884
  "-F",
10885
- "\t",
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("\t");
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 = "\x1F";
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'\\x1f' 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
+ 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 '\x1F') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
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
- headerTitle.textContent = `Schema: ${table2}`;
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 (children.children.length === 0) {
19732
- renderColumns(children, tableName);
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(children, table2.name);
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
- "Column definitions, indexes, foreign keys, triggers, and DDL, with an in-tab refresh action for reloading the current table structure."
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 スキーマセレクター、テーブルツリー(展開でカラム表示)、フィルター、Rails 命名規約による仮想 FK 推測トグル、Query / ER / Search / Snapshot アイコンツールバー。"
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
- "カラム定義、インデックス、外部キー、トリガー、DDL。現在の表構造だけを再読み込みするタブ内更新にも対応。"
26832
+ "テーブルコメント(あれば)、カラム定義、インデックス、外部キー、トリガー、DDL。現在の表構造だけを再読み込みするタブ内更新にも対応。"
26813
26833
  ],
26814
26834
  [
26815
26835
  "クエリエディター",