@youtyan/code-viewer 0.1.53 → 0.1.54

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.
@@ -3337,6 +3337,15 @@ function openDockerAdapter(serviceName, kind, env, cwd, overrideDatabase) {
3337
3337
  var init_docker = () => {};
3338
3338
 
3339
3339
  // web-src/server/database/adapters/sqlite.ts
3340
+ function safePrepare(db, sql) {
3341
+ const stmt = db.prepare(sql);
3342
+ if (typeof stmt.safeIntegers === "function") {
3343
+ try {
3344
+ stmt.safeIntegers(true);
3345
+ } catch {}
3346
+ }
3347
+ return stmt;
3348
+ }
3340
3349
  async function getSqliteClass() {
3341
3350
  if (cachedDbClass)
3342
3351
  return cachedDbClass;
@@ -3450,7 +3459,7 @@ function createSqliteAdapter(db) {
3450
3459
  getTablePage(table, options) {
3451
3460
  const order = buildOrderClause2(options.orderBy);
3452
3461
  const sql = `SELECT * FROM ${sanitizeIdentifier2(table)}${order} LIMIT ? OFFSET ?`;
3453
- const rows = db.prepare(sql).all(options.limit, options.offset);
3462
+ const rows = safePrepare(db, sql).all(options.limit, options.offset);
3454
3463
  const cols = queryColumns(db, table);
3455
3464
  if (rows.length === 0) {
3456
3465
  return {
@@ -3484,11 +3493,11 @@ function createSqliteAdapter(db) {
3484
3493
  const wrappedSql = `SELECT * FROM (${limited}) LIMIT ${maxRows + 1}`;
3485
3494
  let rows;
3486
3495
  try {
3487
- rows = db.prepare(wrappedSql).all(...params || []);
3496
+ rows = safePrepare(db, wrappedSql).all(...params || []);
3488
3497
  } catch (wrapErr) {
3489
3498
  const fallbackSql = `${limited} LIMIT ${maxRows + 1}`;
3490
3499
  try {
3491
- rows = db.prepare(fallbackSql).all(...params || []);
3500
+ rows = safePrepare(db, fallbackSql).all(...params || []);
3492
3501
  } catch {
3493
3502
  throw wrapErr;
3494
3503
  }
@@ -3839,6 +3848,37 @@ var init_discovery = __esm(() => {
3839
3848
  ];
3840
3849
  });
3841
3850
 
3851
+ // web-src/server/database/serialize.ts
3852
+ function serializeDbValue(value) {
3853
+ if (value === null || value === undefined)
3854
+ return null;
3855
+ if (typeof value === "bigint") {
3856
+ return value >= MIN_SAFE && value <= MAX_SAFE ? Number(value) : value.toString();
3857
+ }
3858
+ if (value instanceof Uint8Array) {
3859
+ return `<blob ${value.byteLength} bytes>`;
3860
+ }
3861
+ if (typeof value === "object") {
3862
+ try {
3863
+ return JSON.stringify(value);
3864
+ } catch {
3865
+ return String(value);
3866
+ }
3867
+ }
3868
+ return value;
3869
+ }
3870
+ function serializeDbRow(row) {
3871
+ return row.map(serializeDbValue);
3872
+ }
3873
+ function serializeDbRows(rows) {
3874
+ return rows.map(serializeDbRow);
3875
+ }
3876
+ var MIN_SAFE, MAX_SAFE;
3877
+ var init_serialize = __esm(() => {
3878
+ MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
3879
+ MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
3880
+ });
3881
+
3842
3882
  // web-src/server/database/global-search.ts
3843
3883
  function sanitizeIdentifier3(name, kind) {
3844
3884
  if (kind === "mysql")
@@ -3880,7 +3920,7 @@ function searchTable(adapter, table, columns, term, maxHits, includeNonText, pkC
3880
3920
  const result = kind === "sqlite" ? adapter.executeReadonlyQuery(sql, [`%${escapedTerm}%`], remaining) : adapter.executeReadonlyQuery(sql, undefined, remaining);
3881
3921
  for (const row of result.rows) {
3882
3922
  const colIdx = result.columns.indexOf(col.name);
3883
- const valueRaw = colIdx >= 0 ? row[colIdx] : null;
3923
+ const valueRaw = colIdx >= 0 ? serializeDbValue(row[colIdx]) : null;
3884
3924
  const valueStr = valueRaw == null ? "" : String(valueRaw);
3885
3925
  const preview = valueStr.length > 200 ? `${valueStr.slice(0, 200)}...` : valueStr;
3886
3926
  let rowKeyJson;
@@ -3889,7 +3929,7 @@ function searchTable(adapter, table, columns, term, maxHits, includeNonText, pkC
3889
3929
  for (const pk of pkColumns) {
3890
3930
  const pkIdx = result.columns.indexOf(pk);
3891
3931
  if (pkIdx >= 0)
3892
- keyObj[pk] = row[pkIdx];
3932
+ keyObj[pk] = serializeDbValue(row[pkIdx]);
3893
3933
  }
3894
3934
  rowKeyJson = JSON.stringify(keyObj);
3895
3935
  }
@@ -3898,7 +3938,7 @@ function searchTable(adapter, table, columns, term, maxHits, includeNonText, pkC
3898
3938
  column: col.name,
3899
3939
  rowKeyJson,
3900
3940
  valuePreview: preview,
3901
- rowPreview: row
3941
+ rowPreview: serializeDbRow(row)
3902
3942
  });
3903
3943
  }
3904
3944
  } catch {}
@@ -3909,6 +3949,9 @@ function getPrimaryKeyColumns(adapter, table) {
3909
3949
  const columns = adapter.getColumns(table);
3910
3950
  return columns.filter((c) => c.primaryKey).map((c) => c.name);
3911
3951
  }
3952
+ var init_global_search = __esm(() => {
3953
+ init_serialize();
3954
+ });
3912
3955
 
3913
3956
  // web-src/server/database/query-history.ts
3914
3957
  import {
@@ -4283,6 +4326,8 @@ import { createHash as createHash3 } from "node:crypto";
4283
4326
  function normalizeValue(v) {
4284
4327
  if (v === null)
4285
4328
  return "\\N";
4329
+ if (typeof v === "bigint")
4330
+ return v.toString();
4286
4331
  if (v instanceof Uint8Array) {
4287
4332
  return `\\x${Buffer.from(v).toString("hex")}`;
4288
4333
  }
@@ -4291,7 +4336,7 @@ function normalizeValue(v) {
4291
4336
  function rowToPayloadJson(columns, row) {
4292
4337
  const obj = {};
4293
4338
  for (let i = 0;i < columns.length; i++) {
4294
- obj[columns[i]] = row[i] instanceof Uint8Array ? `<blob ${row[i].byteLength} bytes>` : row[i];
4339
+ obj[columns[i]] = serializeDbValue(row[i]);
4295
4340
  }
4296
4341
  return JSON.stringify(obj);
4297
4342
  }
@@ -4307,7 +4352,7 @@ function buildRowKeyJson(pkColumns, allColumns, row, rowIndex) {
4307
4352
  for (const pk of pkColumns) {
4308
4353
  const idx = allColumns.indexOf(pk);
4309
4354
  if (idx >= 0)
4310
- keyObj[pk] = row[idx];
4355
+ keyObj[pk] = serializeDbValue(row[idx]);
4311
4356
  }
4312
4357
  return JSON.stringify(keyObj);
4313
4358
  }
@@ -4353,6 +4398,7 @@ async function runSnapshot(cwd, adapter, dbId, tables, note, onProgress) {
4353
4398
  }
4354
4399
  var BATCH_SIZE = 500;
4355
4400
  var init_snapshot_runner = __esm(() => {
4401
+ init_serialize();
4356
4402
  init_snapshot_store();
4357
4403
  });
4358
4404
 
@@ -4606,7 +4652,7 @@ async function handleTable(cwd, url) {
4606
4652
  dbId: r.dbId,
4607
4653
  table,
4608
4654
  columns,
4609
- rows: dataResult.rows,
4655
+ rows: serializeDbRows(dataResult.rows),
4610
4656
  totalRows: totalRows2,
4611
4657
  offset,
4612
4658
  limit,
@@ -4621,7 +4667,7 @@ async function handleTable(cwd, url) {
4621
4667
  dbId: r.dbId,
4622
4668
  table,
4623
4669
  columns,
4624
- rows: result.rows,
4670
+ rows: serializeDbRows(result.rows),
4625
4671
  totalRows,
4626
4672
  offset,
4627
4673
  limit,
@@ -4656,11 +4702,12 @@ async function handleQuery(cwd, req, sendSse) {
4656
4702
  const adapter = await getAdapter(r, cwd);
4657
4703
  const result = adapter.executeReadonlyQuery(body.sql, undefined, maxRows);
4658
4704
  const elapsed = Date.now() - start;
4705
+ const serializedRows = serializeDbRows(result.rows);
4659
4706
  const response = {
4660
4707
  dbId: body.db,
4661
4708
  columns: result.columns,
4662
4709
  columnTypes: result.columnTypes,
4663
- rows: result.rows,
4710
+ rows: serializedRows,
4664
4711
  rowCount: result.rowCount,
4665
4712
  truncated: result.rowCount >= maxRows,
4666
4713
  elapsedMs: elapsed
@@ -4673,9 +4720,9 @@ async function handleQuery(cwd, req, sendSse) {
4673
4720
  title: body.title,
4674
4721
  body: body.body,
4675
4722
  columns: result.columns,
4676
- rowsPreview: result.rows,
4723
+ rowsPreview: serializedRows,
4677
4724
  rowCount: result.rowCount,
4678
- savedRows: result.rows.length,
4725
+ savedRows: serializedRows.length,
4679
4726
  truncated: result.rowCount >= maxRows,
4680
4727
  elapsedMs: elapsed,
4681
4728
  executedAt: new Date().toISOString(),
@@ -4750,6 +4797,8 @@ async function handleHistoryClear(cwd, req, sendSse) {
4750
4797
  function formatCsvField(value) {
4751
4798
  if (value === null || value === undefined)
4752
4799
  return "";
4800
+ if (typeof value === "bigint")
4801
+ return value.toString();
4753
4802
  if (value instanceof Uint8Array)
4754
4803
  return `<blob ${value.byteLength} bytes>`;
4755
4804
  const str = typeof value === "object" ? JSON.stringify(value) : String(value);
@@ -4789,7 +4838,7 @@ async function handleExport(cwd, url) {
4789
4838
  if (sortCol && !colNameSet.has(sortCol)) {
4790
4839
  return textError(`invalid sort column: ${sortCol}`, 400);
4791
4840
  }
4792
- let rows;
4841
+ let rawRows;
4793
4842
  if (filters.length > 0) {
4794
4843
  const validFilters = filters.filter((f) => colNameSet.has(f.column));
4795
4844
  if (validFilters.length > 0) {
@@ -4806,14 +4855,14 @@ async function handleExport(cwd, url) {
4806
4855
  const limitOffset = filter.useParams ? "LIMIT ? OFFSET ?" : `LIMIT ${EXPORT_MAX_ROWS} OFFSET 0`;
4807
4856
  const dataSql = `SELECT * FROM ${tbl} WHERE ${filter.where}${order} ${limitOffset}`;
4808
4857
  const result = adapter.executeReadonlyQuery(dataSql, filter.useParams ? [...filter.params, EXPORT_MAX_ROWS, 0] : undefined);
4809
- rows = result.rows;
4858
+ rawRows = result.rows;
4810
4859
  } else {
4811
4860
  const result = adapter.getTablePage(table, {
4812
4861
  offset: 0,
4813
4862
  limit: EXPORT_MAX_ROWS,
4814
4863
  orderBy
4815
4864
  });
4816
- rows = result.rows;
4865
+ rawRows = result.rows;
4817
4866
  }
4818
4867
  } else {
4819
4868
  const result = adapter.getTablePage(table, {
@@ -4821,8 +4870,9 @@ async function handleExport(cwd, url) {
4821
4870
  limit: EXPORT_MAX_ROWS,
4822
4871
  orderBy
4823
4872
  });
4824
- rows = result.rows;
4873
+ rawRows = result.rows;
4825
4874
  }
4875
+ const rows = serializeDbRows(rawRows);
4826
4876
  if (format === "csv") {
4827
4877
  const lines = [colNames.map(formatCsvField).join(",")];
4828
4878
  for (const row of rows) {
@@ -4842,8 +4892,7 @@ async function handleExport(cwd, url) {
4842
4892
  const objects = rows.map((row) => {
4843
4893
  const obj = {};
4844
4894
  for (let i = 0;i < colNames.length; i++) {
4845
- const val = row[i];
4846
- obj[colNames[i]] = val instanceof Uint8Array ? `<blob ${val.byteLength} bytes>` : val;
4895
+ obj[colNames[i]] = row[i];
4847
4896
  }
4848
4897
  return obj;
4849
4898
  });
@@ -5232,7 +5281,9 @@ var init_handle = __esm(() => {
5232
5281
  init_sqlite();
5233
5282
  init_connection_pool();
5234
5283
  init_discovery();
5284
+ init_global_search();
5235
5285
  init_query_history();
5286
+ init_serialize();
5236
5287
  init_snapshot_runner();
5237
5288
  init_snapshot_store();
5238
5289
  dockerAdapterCache = new Map;
@@ -7194,6 +7245,14 @@ data: ok
7194
7245
  });
7195
7246
 
7196
7247
  // web-src/server/cli.ts
7248
+ var REQUIRED_NODE_MAJOR = 20;
7249
+ var nodeMajor = Number.parseInt((process.versions.node || "0").split(".")[0] || "0", 10);
7250
+ if (!Number.isFinite(nodeMajor) || nodeMajor < REQUIRED_NODE_MAJOR) {
7251
+ process.stderr.write(`code-viewer requires Node.js >= ${REQUIRED_NODE_MAJOR}.0.0, but found ${process.versions.node}.
7252
+ ` + `Please upgrade Node.js (e.g. via nvm, volta, or your package manager) and retry.
7253
+ `);
7254
+ process.exit(1);
7255
+ }
7197
7256
  if (process.argv[2] === "annotate") {
7198
7257
  const { runAnnotateCli: runAnnotateCli2 } = await Promise.resolve().then(() => (init_annotate_cli(), exports_annotate_cli));
7199
7258
  await runAnnotateCli2(process.argv.slice(3));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
package/web/app.js CHANGED
@@ -9415,6 +9415,7 @@ ${frontmatter.yaml}
9415
9415
  var PAGE_SIZE = 200;
9416
9416
  var FILTER_DEBOUNCE_MS = 300;
9417
9417
  var DEFAULT_COL_WIDTH = 180;
9418
+ var CELL_PREVIEW_MAX_CHARS = 4000;
9418
9419
  function createTableGrid(callbacks) {
9419
9420
  const el = document.createElement("div");
9420
9421
  el.className = "db-grid";
@@ -9557,11 +9558,17 @@ ${frontmatter.yaml}
9557
9558
  }
9558
9559
  return filters;
9559
9560
  }
9561
+ function resetSelectionAndDetail() {
9562
+ selectedRowIndex = -1;
9563
+ detailPanel.hidden = true;
9564
+ detailPanel.innerHTML = "";
9565
+ }
9560
9566
  function invalidateData() {
9561
9567
  pageCache = new Map;
9562
9568
  pendingPages = new Set;
9563
9569
  loadGeneration++;
9564
9570
  viewport.scrollTop = 0;
9571
+ resetSelectionAndDetail();
9565
9572
  ensurePage(0);
9566
9573
  }
9567
9574
  function clear() {
@@ -9600,6 +9607,22 @@ ${frontmatter.yaml}
9600
9607
  const title = document.createElement("span");
9601
9608
  title.className = "db-grid-detail-title";
9602
9609
  title.textContent = `${colName} (${colType})`;
9610
+ const copyBtn = document.createElement("button");
9611
+ copyBtn.type = "button";
9612
+ copyBtn.className = "db-btn db-grid-detail-copy";
9613
+ copyBtn.textContent = "Copy";
9614
+ const copyText = formatValueForCopy(value);
9615
+ copyBtn.addEventListener("click", () => {
9616
+ navigator.clipboard.writeText(copyText).then(() => {
9617
+ const original = copyBtn.textContent;
9618
+ copyBtn.textContent = "Copied";
9619
+ setTimeout(() => {
9620
+ copyBtn.textContent = original;
9621
+ }, 800);
9622
+ }, () => {
9623
+ copyBtn.textContent = "Copy failed";
9624
+ });
9625
+ });
9603
9626
  const closeBtn = document.createElement("button");
9604
9627
  closeBtn.type = "button";
9605
9628
  closeBtn.className = "db-btn db-btn-icon db-grid-detail-close";
@@ -9607,7 +9630,7 @@ ${frontmatter.yaml}
9607
9630
  closeBtn.addEventListener("click", () => {
9608
9631
  detailPanel.hidden = true;
9609
9632
  });
9610
- header.append(title, closeBtn);
9633
+ header.append(title, copyBtn, closeBtn);
9611
9634
  const content = document.createElement("div");
9612
9635
  content.className = "db-grid-detail-content";
9613
9636
  if (value === null) {
@@ -9769,6 +9792,7 @@ ${frontmatter.yaml}
9769
9792
  }
9770
9793
  pageCache = new Map;
9771
9794
  pendingPages = new Set;
9795
+ resetSelectionAndDetail();
9772
9796
  renderHeader();
9773
9797
  renderViewport();
9774
9798
  }
@@ -9852,11 +9876,6 @@ ${frontmatter.yaml}
9852
9876
  });
9853
9877
  row.classList.add("selected");
9854
9878
  showCellDetail(cellColIndex, cellValue);
9855
- const text2 = formatValueForCopy(cellValue);
9856
- navigator.clipboard.writeText(text2).then(() => {
9857
- cell.classList.add("copied");
9858
- setTimeout(() => cell.classList.remove("copied"), 600);
9859
- }, () => {});
9860
9879
  });
9861
9880
  row.appendChild(cell);
9862
9881
  }
@@ -9994,6 +10013,10 @@ ${frontmatter.yaml}
9994
10013
  const s2 = String(value);
9995
10014
  if (s2 === "")
9996
10015
  return "<empty>";
10016
+ if (s2.length > CELL_PREVIEW_MAX_CHARS) {
10017
+ const truncated = s2.slice(0, CELL_PREVIEW_MAX_CHARS);
10018
+ return `${truncated} … (+${s2.length - CELL_PREVIEW_MAX_CHARS} chars)`;
10019
+ }
9997
10020
  return s2;
9998
10021
  }
9999
10022
  function formatValueForCopy(value) {
package/web/style.css CHANGED
@@ -6116,6 +6116,16 @@ html[data-theme="dark"] .db-query-highlight span[style] {
6116
6116
  color: var(--danger);
6117
6117
  background: none;
6118
6118
  }
6119
+ .db-grid-detail-copy {
6120
+ font-size: 11px;
6121
+ padding: 2px 8px;
6122
+ margin-left: auto;
6123
+ margin-right: 8px;
6124
+ color: var(--fg-muted);
6125
+ }
6126
+ .db-grid-detail-copy:hover {
6127
+ color: var(--fg);
6128
+ }
6119
6129
 
6120
6130
  .db-grid-detail-content {
6121
6131
  padding: 8px 12px;