@youtyan/code-viewer 0.6.1 → 0.6.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 CHANGED
@@ -251,8 +251,10 @@ Open Datastores in the global navigation to access:
251
251
  the database.
252
252
  - **Table browser** — paginated data grid with column sort, text filter, cell
253
253
  copy, and CSV/JSON export (capped at 100,000 rows; export respects current
254
- filter/sort). Double-click a cell to edit inline when Edit mode is on; the
255
- whole pending edit batch commits atomically.
254
+ filter/sort). Reload just the current table from the search bar while
255
+ keeping global search and column filters, with a result chip that calls out
256
+ row-count changes. Double-click a cell to edit inline when Edit mode is on;
257
+ the whole pending edit batch commits atomically.
256
258
  - **Detail footer and related panel** — click any cell to open a resizable
257
259
  detail footer; foreign-key cells open a related panel showing the
258
260
  referenced or referencing rows, with multi-step drill-down breadcrumbs.
@@ -276,8 +278,9 @@ Open Datastores in the global navigation to access:
276
278
  - **ER diagram** — auto-generated entity-relationship diagram showing
277
279
  foreign-key relationships between tables.
278
280
  - **Schema view** — table columns (with column comments when the database
279
- defines them), indexes, foreign keys, triggers, and DDL. Tables themselves
280
- surface a comment column on the database table list.
281
+ defines them), indexes, foreign keys, triggers, and DDL, with an in-tab
282
+ schema refresh action. Tables themselves surface a comment column on the
283
+ database table list.
281
284
  - **Global search** — full-text search across all tables and text columns of
282
285
  a database.
283
286
  - **Snapshots and diffs** — take point-in-time snapshots of selected tables
@@ -1438,7 +1438,19 @@ function omittedWorktreeDirectoryReason(name, omitDirNames) {
1438
1438
  return "internal";
1439
1439
  return omitDirNames.has(name) ? "heavy" : undefined;
1440
1440
  }
1441
- function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames) {
1441
+ function worktreeSubmodulePaths(cwd) {
1442
+ if (!existsSync(join2(cwd, ".gitmodules")))
1443
+ return new Set;
1444
+ const res = run(["git", "config", "--file", ".gitmodules", "--get-regexp", "\\.path$"], cwd);
1445
+ if (res.code !== 0)
1446
+ return new Set;
1447
+ return new Set(res.stdout.split(`
1448
+ `).map((line) => {
1449
+ const split = line.indexOf(" ");
1450
+ return split >= 0 ? normalizeTreePath(line.slice(split + 1)) : "";
1451
+ }).filter(Boolean));
1452
+ }
1453
+ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames, submodulePaths) {
1442
1454
  if (excludeNames.has(name.toLowerCase()))
1443
1455
  return {
1444
1456
  name,
@@ -1448,23 +1460,24 @@ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, exc
1448
1460
  const entryPath = base ? `${base}/${name}` : name;
1449
1461
  const type = isDirectory ? hasDotGitEntry(join2(dir, name)) ? "commit" : "tree" : "blob";
1450
1462
  const omittedReason = type === "tree" ? omittedWorktreeDirectoryReason(name, omitDirNames) : undefined;
1463
+ const submodule = type === "commit" && submodulePaths.has(entryPath) ? true : undefined;
1464
+ const baseEntry = submodule ? { name, path: entryPath, type, submodule } : { name, path: entryPath, type };
1451
1465
  return omittedReason ? {
1452
- name,
1453
- path: entryPath,
1454
- type,
1466
+ ...baseEntry,
1455
1467
  children_omitted: true,
1456
1468
  children_omitted_reason: omittedReason
1457
- } : { name, path: entryPath, type };
1469
+ } : baseEntry;
1458
1470
  }
1459
1471
  function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
1460
1472
  const base = normalizeTreePath(path);
1461
1473
  const root = join2(cwd, base);
1462
1474
  const omitDirNameSet = new Set(omitDirNames);
1463
1475
  const excludeNameSet = new Set(excludeNames.map((name) => name.toLowerCase()));
1476
+ const submodulePaths = worktreeSubmodulePaths(cwd);
1464
1477
  let directEntries;
1465
1478
  try {
1466
1479
  const dirents = readdirSync(root, { withFileTypes: true });
1467
- directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet, excludeNameSet)).filter((entry) => entry.path));
1480
+ directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
1468
1481
  } catch {
1469
1482
  return [];
1470
1483
  }
@@ -15437,6 +15450,38 @@ async function handleTable(cwd, url, omitDirNames, signal) {
15437
15450
  return handleError("database", "read table", err, signal);
15438
15451
  }
15439
15452
  }
15453
+ async function handleTableCount(cwd, url, omitDirNames, signal) {
15454
+ const r = await resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"), signal);
15455
+ if (r instanceof Response)
15456
+ return r;
15457
+ const table = url.searchParams.get("table");
15458
+ if (!table)
15459
+ return textError("missing table parameter", 400);
15460
+ try {
15461
+ const adapter = await getAdapter(r, cwd, signal);
15462
+ const { result, executedSql } = await captureSql(async () => {
15463
+ const db = asAsync(adapter);
15464
+ const tables = await db.tables(signal);
15465
+ const entry = tables.find((candidate) => candidate.name === table);
15466
+ if (!entry)
15467
+ throw new Error(`unknown table: ${table}`);
15468
+ if (entry.type !== "table")
15469
+ return { rowCount: null };
15470
+ const counts = await db.tableRowCounts([table], signal);
15471
+ return { rowCount: counts.get(table) ?? null };
15472
+ });
15473
+ const body = {
15474
+ dbId: r.dbId,
15475
+ ...r.schema ? { schema: r.schema } : {},
15476
+ table,
15477
+ rowCount: result.rowCount,
15478
+ executedSql
15479
+ };
15480
+ return json(body);
15481
+ } catch (err) {
15482
+ return handleError("database", "read table count", err, signal);
15483
+ }
15484
+ }
15440
15485
  function makeHistoryId() {
15441
15486
  return makeId("qh");
15442
15487
  }
@@ -16365,6 +16410,10 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
16365
16410
  methods: ["GET"],
16366
16411
  handler: () => handleTable(cwd, url, omitDirNames, req.signal)
16367
16412
  },
16413
+ "/_db/table-count": {
16414
+ methods: ["GET"],
16415
+ handler: () => handleTableCount(cwd, url, omitDirNames, req.signal)
16416
+ },
16368
16417
  "/_db/columns": {
16369
16418
  methods: ["GET"],
16370
16419
  handler: () => handleColumns(cwd, url, omitDirNames, req.signal)
@@ -19983,6 +20032,8 @@ function fileMetadataForTarget(target, path) {
19983
20032
  function attachTreeEntryMetadata(target, entry) {
19984
20033
  if (entry.type === "tree")
19985
20034
  return { ...entry, ...directoryMetadata(target, entry.path) };
20035
+ if (entry.type === "commit" && !entry.submodule && (target === "worktree" || target === ""))
20036
+ return { ...entry, ...directoryMetadata(target, entry.path) };
19986
20037
  if (entry.type !== "blob")
19987
20038
  return entry;
19988
20039
  return { ...entry, ...fileMetadataForTarget(target, entry.path) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {