@youtyan/code-viewer 0.6.9 → 0.7.0

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/web/app.js CHANGED
@@ -8494,6 +8494,9 @@ ${frontmatter.yaml}
8494
8494
  let ANNOTATIONS = { version: 1, sessions: [] };
8495
8495
  let annotationFollow = deps.getAnnotationFollow();
8496
8496
  let activeAnnotationId = null;
8497
+ let refreshAnnotationsInFlight = null;
8498
+ let inlineAnnotationsMounted = false;
8499
+ let databaseAnnotationsMounted = false;
8497
8500
  let annotationPanelDismissed = false;
8498
8501
  let activeSessionId = new URLSearchParams(window.location.search).get(ANNOTATION_SESSION_PARAM);
8499
8502
  const annotationsChangedCallbacks = [];
@@ -8658,9 +8661,12 @@ ${frontmatter.yaml}
8658
8661
  return box;
8659
8662
  }
8660
8663
  function applyDatabaseAnnotations(session) {
8661
- document.querySelectorAll(".gdp-db-annotation-strip").forEach((el) => {
8662
- el.remove();
8663
- });
8664
+ if (databaseAnnotationsMounted) {
8665
+ document.querySelectorAll(".gdp-db-annotation-strip").forEach((el) => {
8666
+ el.remove();
8667
+ });
8668
+ databaseAnnotationsMounted = false;
8669
+ }
8664
8670
  if (!session || deps.getRoute().screen !== "database")
8665
8671
  return;
8666
8672
  const matches = session.entries.filter(databaseAnnotationMatchesRoute);
@@ -8676,6 +8682,7 @@ ${frontmatter.yaml}
8676
8682
  strip.appendChild(buildDatabaseAnnotationBlock(entry));
8677
8683
  }
8678
8684
  root.prepend(strip);
8685
+ databaseAnnotationsMounted = true;
8679
8686
  }
8680
8687
  function inlineAnnotationTargetRow(entry) {
8681
8688
  if (!entry.line)
@@ -8734,13 +8741,19 @@ ${frontmatter.yaml}
8734
8741
  return tr;
8735
8742
  }
8736
8743
  function applyInlineAnnotations() {
8737
- document.querySelectorAll(".gdp-annotation-row").forEach((row) => {
8738
- row.remove();
8739
- });
8740
8744
  const session = ANNOTATIONS.sessions.find((s2) => s2.id === activeSessionId);
8745
+ if (!session && !inlineAnnotationsMounted && !databaseAnnotationsMounted)
8746
+ return;
8747
+ if (inlineAnnotationsMounted) {
8748
+ document.querySelectorAll(".gdp-annotation-row").forEach((row) => {
8749
+ row.remove();
8750
+ });
8751
+ inlineAnnotationsMounted = false;
8752
+ }
8741
8753
  applyDatabaseAnnotations(session);
8742
8754
  if (!session)
8743
8755
  return;
8756
+ let mountedInlineRows = false;
8744
8757
  for (const entry of session.entries) {
8745
8758
  if (entry.target?.kind === "database")
8746
8759
  continue;
@@ -8758,7 +8771,9 @@ ${frontmatter.yaml}
8758
8771
  sibAnchor = sibAnchor.nextElementSibling;
8759
8772
  sibAnchor.after(buildInlineSpacerRow(entry, sibling.cells.length));
8760
8773
  }
8774
+ mountedInlineRows = true;
8761
8775
  }
8776
+ inlineAnnotationsMounted = mountedInlineRows;
8762
8777
  syncInlineAnnotationWidths();
8763
8778
  }
8764
8779
  function syncInlineAnnotationSpacerHeights() {
@@ -8883,7 +8898,17 @@ ${frontmatter.yaml}
8883
8898
  annotationCountEl.hidden = total === 0;
8884
8899
  annotationListCountEl.textContent = `${ANNOTATIONS.sessions.length} sessions / ${total} annotations`;
8885
8900
  }
8886
- async function refreshAnnotations() {
8901
+ function refreshAnnotations() {
8902
+ if (refreshAnnotationsInFlight)
8903
+ return refreshAnnotationsInFlight;
8904
+ const started = doRefreshAnnotations().finally(() => {
8905
+ if (refreshAnnotationsInFlight === started)
8906
+ refreshAnnotationsInFlight = null;
8907
+ });
8908
+ refreshAnnotationsInFlight = started;
8909
+ return started;
8910
+ }
8911
+ async function doRefreshAnnotations() {
8887
8912
  try {
8888
8913
  const res = await fetch("/_annotations");
8889
8914
  if (!res.ok)
@@ -10327,6 +10352,24 @@ ${frontmatter.yaml}
10327
10352
  confirmDeleteObject: (key) => `Delete object "${key}"?`,
10328
10353
  editTextHint: "Only text objects can be edited in the browser.",
10329
10354
  create: "Create"
10355
+ },
10356
+ dynamodb: {
10357
+ table: "Table",
10358
+ scanMode: "Scan",
10359
+ queryMode: "Query",
10360
+ keyConditionPlaceholder: "Key condition expression, e.g. pk = :pk",
10361
+ filterPlaceholder: "Filter expression (optional)",
10362
+ attributeValuesPlaceholder: '{":pk": {"S": "value"}}',
10363
+ sortAsc: "Ascending",
10364
+ sortDesc: "Descending",
10365
+ selectItem: "Select an item to preview.",
10366
+ noItems: "(no items)",
10367
+ noTables: "(no tables)",
10368
+ copyKey: "Copy key",
10369
+ copied: "Copied",
10370
+ copyFailed: "Copy failed",
10371
+ invalidAttributeValues: "Invalid attribute values JSON",
10372
+ runQuery: "Run"
10330
10373
  }
10331
10374
  }
10332
10375
  };
@@ -10649,6 +10692,24 @@ ${frontmatter.yaml}
10649
10692
  confirmDeleteObject: (key) => `オブジェクト "${key}" を削除しますか?`,
10650
10693
  editTextHint: "ブラウザで編集できるのはテキストオブジェクトのみです。",
10651
10694
  create: "作成"
10695
+ },
10696
+ dynamodb: {
10697
+ table: "テーブル",
10698
+ scanMode: "Scan",
10699
+ queryMode: "Query",
10700
+ keyConditionPlaceholder: "キー条件式 例: pk = :pk",
10701
+ filterPlaceholder: "フィルタ式 (任意)",
10702
+ attributeValuesPlaceholder: '{":pk": {"S": "value"}}',
10703
+ sortAsc: "昇順",
10704
+ sortDesc: "降順",
10705
+ selectItem: "アイテムを選択するとプレビューが表示されます。",
10706
+ noItems: "(アイテムがありません)",
10707
+ noTables: "(テーブルがありません)",
10708
+ copyKey: "キーをコピー",
10709
+ copied: "コピーしました",
10710
+ copyFailed: "コピーに失敗しました",
10711
+ invalidAttributeValues: "属性値の JSON が不正です",
10712
+ runQuery: "実行"
10652
10713
  }
10653
10714
  }
10654
10715
  };
@@ -10686,6 +10747,706 @@ ${frontmatter.yaml}
10686
10747
  el.appendChild(empty);
10687
10748
  }
10688
10749
 
10750
+ // web-src/views/database/dynamodb-explorer.ts
10751
+ function unwrapAttributeValue(av) {
10752
+ if (!av)
10753
+ return;
10754
+ if ("S" in av)
10755
+ return av.S;
10756
+ if ("N" in av)
10757
+ return av.N;
10758
+ if ("BOOL" in av)
10759
+ return av.BOOL;
10760
+ if ("NULL" in av)
10761
+ return null;
10762
+ if ("B" in av)
10763
+ return "(binary)";
10764
+ if ("SS" in av)
10765
+ return av.SS;
10766
+ if ("NS" in av)
10767
+ return av.NS;
10768
+ if ("BS" in av)
10769
+ return "(binary set)";
10770
+ if ("L" in av)
10771
+ return av.L.map(unwrapAttributeValue);
10772
+ if ("M" in av) {
10773
+ const out = {};
10774
+ for (const [k, v] of Object.entries(av.M))
10775
+ out[k] = unwrapAttributeValue(v);
10776
+ return out;
10777
+ }
10778
+ return;
10779
+ }
10780
+ function unwrapItem(item) {
10781
+ const out = {};
10782
+ for (const [k, v] of Object.entries(item))
10783
+ out[k] = unwrapAttributeValue(v);
10784
+ return out;
10785
+ }
10786
+ function previewItem(item) {
10787
+ const entries = Object.entries(item).slice(0, 6);
10788
+ const parts = entries.map(([key, rawValue]) => {
10789
+ const value = unwrapAttributeValue(rawValue);
10790
+ const text2 = value !== null && typeof value === "object" ? JSON.stringify(value) : String(value);
10791
+ return `${key}=${text2.length > 40 ? `${text2.slice(0, 40)}...` : text2}`;
10792
+ });
10793
+ return parts.join(" / ");
10794
+ }
10795
+ function extractItemKey(item, keySchema) {
10796
+ if (!keySchema || keySchema.length === 0)
10797
+ return item;
10798
+ const key = {};
10799
+ for (const k of keySchema) {
10800
+ if (item[k.AttributeName] !== undefined)
10801
+ key[k.AttributeName] = item[k.AttributeName];
10802
+ }
10803
+ return Object.keys(key).length > 0 ? key : item;
10804
+ }
10805
+ function itemKeyToken(key) {
10806
+ const sorted = {};
10807
+ for (const k of Object.keys(key).sort())
10808
+ sorted[k] = key[k];
10809
+ return JSON.stringify(sorted);
10810
+ }
10811
+ function parseAttributeValuesJson(raw) {
10812
+ if (!raw.trim())
10813
+ return {};
10814
+ try {
10815
+ const parsed = JSON.parse(raw);
10816
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
10817
+ return null;
10818
+ }
10819
+ return parsed;
10820
+ } catch {
10821
+ return null;
10822
+ }
10823
+ }
10824
+ function createDynamoDbExplorer(callbacks = {}) {
10825
+ const text2 = () => (callbacks.getText?.() ?? dbText("en")).explorer;
10826
+ const trackLoad = (promise) => callbacks.trackLoad ? callbacks.trackLoad(promise) : promise;
10827
+ const container = document.createElement("div");
10828
+ container.className = "dynamodb-explorer";
10829
+ const sidebarSlot = document.createElement("div");
10830
+ sidebarSlot.className = "db-explorer-sidebar-slot dynamodb-table-list-pane";
10831
+ const tableListHeader = document.createElement("div");
10832
+ tableListHeader.className = "db-explorer-pane-header";
10833
+ tableListHeader.textContent = text2().dynamodb.table;
10834
+ sidebarSlot.appendChild(tableListHeader);
10835
+ const tableList = document.createElement("div");
10836
+ tableList.className = "dynamodb-table-list";
10837
+ sidebarSlot.appendChild(tableList);
10838
+ const tableMoreBtn = document.createElement("button");
10839
+ tableMoreBtn.type = "button";
10840
+ tableMoreBtn.className = "dynamodb-item-more-btn dynamodb-table-more-btn";
10841
+ tableMoreBtn.textContent = text2().common.loadMore;
10842
+ tableMoreBtn.hidden = true;
10843
+ sidebarSlot.appendChild(tableMoreBtn);
10844
+ const itemListPane = document.createElement("div");
10845
+ itemListPane.className = "dynamodb-item-list-pane";
10846
+ const modeSeg = document.createElement("div");
10847
+ modeSeg.className = "seg dynamodb-mode-seg";
10848
+ const scanModeBtn = document.createElement("button");
10849
+ scanModeBtn.type = "button";
10850
+ scanModeBtn.textContent = text2().dynamodb.scanMode;
10851
+ const queryModeBtn = document.createElement("button");
10852
+ queryModeBtn.type = "button";
10853
+ queryModeBtn.textContent = text2().dynamodb.queryMode;
10854
+ modeSeg.append(scanModeBtn, queryModeBtn);
10855
+ itemListPane.appendChild(modeSeg);
10856
+ const queryForm = document.createElement("form");
10857
+ queryForm.className = "dynamodb-query-form";
10858
+ const keyConditionInput = document.createElement("input");
10859
+ keyConditionInput.type = "text";
10860
+ keyConditionInput.className = "dynamodb-key-condition-input";
10861
+ keyConditionInput.placeholder = text2().dynamodb.keyConditionPlaceholder;
10862
+ keyConditionInput.autocomplete = "off";
10863
+ keyConditionInput.hidden = true;
10864
+ const filterInput = document.createElement("input");
10865
+ filterInput.type = "text";
10866
+ filterInput.className = "dynamodb-filter-input";
10867
+ filterInput.placeholder = text2().dynamodb.filterPlaceholder;
10868
+ filterInput.autocomplete = "off";
10869
+ const attributeValuesInput = document.createElement("textarea");
10870
+ attributeValuesInput.className = "dynamodb-attribute-values-input";
10871
+ attributeValuesInput.placeholder = text2().dynamodb.attributeValuesPlaceholder;
10872
+ attributeValuesInput.rows = 2;
10873
+ const runBtn = document.createElement("button");
10874
+ runBtn.type = "submit";
10875
+ runBtn.className = "db-btn db-btn-primary dynamodb-run-btn";
10876
+ runBtn.textContent = text2().dynamodb.runQuery;
10877
+ queryForm.append(keyConditionInput, filterInput, attributeValuesInput, runBtn);
10878
+ itemListPane.appendChild(queryForm);
10879
+ const queryError = document.createElement("div");
10880
+ queryError.className = "dynamodb-query-error";
10881
+ queryError.hidden = true;
10882
+ itemListPane.appendChild(queryError);
10883
+ const itemStatus = document.createElement("div");
10884
+ itemStatus.className = "dynamodb-item-status";
10885
+ itemListPane.appendChild(itemStatus);
10886
+ const itemList = document.createElement("div");
10887
+ itemList.className = "dynamodb-item-list";
10888
+ itemListPane.appendChild(itemList);
10889
+ const moreBtn = document.createElement("button");
10890
+ moreBtn.type = "button";
10891
+ moreBtn.className = "dynamodb-item-more-btn";
10892
+ moreBtn.textContent = text2().common.loadMore;
10893
+ moreBtn.hidden = true;
10894
+ itemListPane.appendChild(moreBtn);
10895
+ const detailPane = document.createElement("div");
10896
+ detailPane.className = "dynamodb-detail-pane";
10897
+ setPaneEmpty(detailPane, text2().dynamodb.selectItem);
10898
+ container.append(itemListPane, detailPane);
10899
+ let currentDbId = null;
10900
+ let currentTable = null;
10901
+ let currentMode = "scan";
10902
+ let currentScanIndexForward = true;
10903
+ let currentTableInfo = null;
10904
+ let currentTableNextToken;
10905
+ let currentNextToken;
10906
+ let currentItemKeyToken = null;
10907
+ let cumulativeShownCount = 0;
10908
+ let cumulativeScannedCount = 0;
10909
+ let disposed = false;
10910
+ let loadRunId = 0;
10911
+ let itemRunId = 0;
10912
+ let suppressNotify = false;
10913
+ let activeTableRow = null;
10914
+ let activeItemRow = null;
10915
+ const itemsByKeyToken = new Map;
10916
+ const itemRowsByKeyToken = new Map;
10917
+ const tableGuard = createAbortGuard();
10918
+ const tablePageGuard = createAbortGuard();
10919
+ const tableInfoGuard = createAbortGuard();
10920
+ const itemsGuard = createAbortGuard();
10921
+ const itemGuard = createAbortGuard();
10922
+ function notifySelectionChange() {
10923
+ if (suppressNotify)
10924
+ return;
10925
+ callbacks.onSelectionChange?.(getSelection());
10926
+ }
10927
+ function setMode(mode) {
10928
+ currentMode = mode;
10929
+ scanModeBtn.classList.toggle("active", mode === "scan");
10930
+ queryModeBtn.classList.toggle("active", mode === "query");
10931
+ keyConditionInput.hidden = mode !== "query";
10932
+ }
10933
+ function renderTables(tableNames, append = false) {
10934
+ if (!append) {
10935
+ tableList.innerHTML = "";
10936
+ activeTableRow = null;
10937
+ }
10938
+ if (tableNames.length === 0 && !append) {
10939
+ setPaneStatus(tableList, text2().dynamodb.noTables);
10940
+ return;
10941
+ }
10942
+ const fragment = document.createDocumentFragment();
10943
+ for (const name of tableNames) {
10944
+ const row = document.createElement("div");
10945
+ row.className = "dynamodb-table-item";
10946
+ row.dataset.tableName = name;
10947
+ row.textContent = name;
10948
+ row.title = name;
10949
+ fragment.appendChild(row);
10950
+ }
10951
+ tableList.appendChild(fragment);
10952
+ }
10953
+ function highlightActiveTable(name) {
10954
+ if (activeTableRow?.dataset.tableName === name)
10955
+ return;
10956
+ activeTableRow?.classList.remove("active");
10957
+ activeTableRow = tableList.querySelector(`[data-table-name="${CSS.escape(name)}"]`) ?? null;
10958
+ activeTableRow?.classList.add("active");
10959
+ }
10960
+ function highlightActiveItem(token) {
10961
+ if (activeItemRow && activeItemRow.dataset.keyToken === token)
10962
+ return;
10963
+ activeItemRow?.classList.remove("active");
10964
+ activeItemRow = token ? itemRowsByKeyToken.get(token) ?? null : null;
10965
+ activeItemRow?.classList.add("active");
10966
+ }
10967
+ function appendItems(items) {
10968
+ const fragment = document.createDocumentFragment();
10969
+ for (const item of items) {
10970
+ const key = extractItemKey(item, currentTableInfo?.KeySchema);
10971
+ const token = itemKeyToken(key);
10972
+ itemsByKeyToken.set(token, item);
10973
+ const row = document.createElement("div");
10974
+ row.className = "dynamodb-item-row";
10975
+ row.dataset.keyToken = token;
10976
+ itemRowsByKeyToken.set(token, row);
10977
+ const preview = document.createElement("span");
10978
+ preview.className = "dynamodb-item-preview";
10979
+ preview.textContent = previewItem(item);
10980
+ row.appendChild(preview);
10981
+ fragment.appendChild(row);
10982
+ }
10983
+ itemList.appendChild(fragment);
10984
+ }
10985
+ function renderTableInfo() {
10986
+ detailPane.innerHTML = "";
10987
+ if (!currentTableInfo)
10988
+ return;
10989
+ const header = document.createElement("div");
10990
+ header.className = "dynamodb-table-info-header";
10991
+ header.textContent = currentTableInfo.TableName ?? currentTable ?? "";
10992
+ const meta = document.createElement("div");
10993
+ meta.className = "dynamodb-table-info-meta";
10994
+ meta.textContent = [
10995
+ currentTableInfo.TableStatus,
10996
+ currentTableInfo.ItemCount !== undefined ? `${currentTableInfo.ItemCount.toLocaleString()} items` : undefined
10997
+ ].filter(Boolean).join(" / ");
10998
+ detailPane.append(header, meta);
10999
+ const keySchema = currentTableInfo.KeySchema ?? [];
11000
+ if (keySchema.length > 0) {
11001
+ const keyList = document.createElement("div");
11002
+ keyList.className = "dynamodb-table-info-keys";
11003
+ keyList.textContent = keySchema.map((k) => `${k.AttributeName} (${k.KeyType})`).join(", ");
11004
+ detailPane.appendChild(keyList);
11005
+ }
11006
+ const empty = document.createElement("div");
11007
+ empty.className = "db-pane-empty dynamodb-table-info-empty";
11008
+ const emptyTitle = document.createElement("div");
11009
+ emptyTitle.className = "db-pane-empty-title";
11010
+ emptyTitle.textContent = text2().dynamodb.selectItem;
11011
+ empty.appendChild(emptyTitle);
11012
+ detailPane.appendChild(empty);
11013
+ }
11014
+ function renderItemDetail(item) {
11015
+ detailPane.innerHTML = "";
11016
+ const header = document.createElement("div");
11017
+ header.className = "dynamodb-item-detail-header";
11018
+ const title = document.createElement("span");
11019
+ title.textContent = currentTable ?? "";
11020
+ const copyBtn = document.createElement("button");
11021
+ copyBtn.type = "button";
11022
+ copyBtn.className = "db-btn db-btn-sm dynamodb-copy-key-btn";
11023
+ copyBtn.textContent = text2().dynamodb.copyKey;
11024
+ const copyStatus = document.createElement("span");
11025
+ copyStatus.className = "dynamodb-copy-status";
11026
+ copyStatus.setAttribute("aria-live", "polite");
11027
+ copyBtn.addEventListener("click", async () => {
11028
+ copyStatus.textContent = "";
11029
+ try {
11030
+ const key = extractItemKey(item, currentTableInfo?.KeySchema);
11031
+ await navigator.clipboard.writeText(JSON.stringify(key));
11032
+ copyStatus.textContent = text2().dynamodb.copied;
11033
+ } catch {
11034
+ copyStatus.textContent = text2().dynamodb.copyFailed;
11035
+ }
11036
+ });
11037
+ header.append(title, copyBtn, copyStatus);
11038
+ detailPane.appendChild(header);
11039
+ const pre = document.createElement("pre");
11040
+ pre.className = "dynamodb-item-source";
11041
+ try {
11042
+ pre.textContent = JSON.stringify(unwrapItem(item), null, 2);
11043
+ } catch {
11044
+ pre.textContent = String(item);
11045
+ }
11046
+ detailPane.appendChild(pre);
11047
+ }
11048
+ function selectItem(item, token) {
11049
+ currentItemKeyToken = token ?? itemKeyToken(extractItemKey(item, currentTableInfo?.KeySchema));
11050
+ highlightActiveItem(currentItemKeyToken);
11051
+ renderItemDetail(item);
11052
+ notifySelectionChange();
11053
+ }
11054
+ async function loadItems(append) {
11055
+ if (!currentDbId || !currentTable || disposed)
11056
+ return;
11057
+ const keyConditionExpression = keyConditionInput.value.trim();
11058
+ const filterExpression = filterInput.value.trim();
11059
+ const attributeValues = parseAttributeValuesJson(attributeValuesInput.value);
11060
+ queryError.hidden = true;
11061
+ if (currentMode === "query" && !keyConditionExpression) {
11062
+ queryError.hidden = false;
11063
+ queryError.textContent = text2().dynamodb.keyConditionPlaceholder;
11064
+ return;
11065
+ }
11066
+ if (attributeValues === null) {
11067
+ queryError.hidden = false;
11068
+ queryError.textContent = text2().dynamodb.invalidAttributeValues;
11069
+ return;
11070
+ }
11071
+ const slot = itemsGuard.start();
11072
+ const requestRunId = loadRunId;
11073
+ const requestDbId = currentDbId;
11074
+ const requestTable = currentTable;
11075
+ const requestMode = currentMode;
11076
+ moreBtn.disabled = true;
11077
+ if (!append) {
11078
+ itemList.innerHTML = "";
11079
+ itemsByKeyToken.clear();
11080
+ itemRowsByKeyToken.clear();
11081
+ activeItemRow = null;
11082
+ currentItemKeyToken = null;
11083
+ currentNextToken = undefined;
11084
+ cumulativeShownCount = 0;
11085
+ cumulativeScannedCount = 0;
11086
+ setPaneStatus(itemList, "Loading items...");
11087
+ if (!currentTableInfo)
11088
+ setPaneEmpty(detailPane, text2().dynamodb.selectItem);
11089
+ }
11090
+ try {
11091
+ const params = new URLSearchParams({
11092
+ db: requestDbId,
11093
+ table: requestTable,
11094
+ mode: requestMode,
11095
+ limit: "200"
11096
+ });
11097
+ if (filterExpression)
11098
+ params.set("filterExpression", filterExpression);
11099
+ if (Object.keys(attributeValues).length > 0) {
11100
+ params.set("expressionAttributeValues", JSON.stringify(attributeValues));
11101
+ }
11102
+ if (requestMode === "query") {
11103
+ params.set("keyConditionExpression", keyConditionExpression);
11104
+ params.set("scanIndexForward", String(currentScanIndexForward));
11105
+ }
11106
+ if (append && currentNextToken) {
11107
+ params.set("exclusiveStartKey", JSON.stringify(currentNextToken));
11108
+ }
11109
+ const res = await trackLoad(fetch(`/_db/dynamodb/items?${params}`, { signal: slot.signal }));
11110
+ if (disposed || slot.isStale())
11111
+ return;
11112
+ if (!res.ok) {
11113
+ const errText = await res.text();
11114
+ setPaneStatus(itemList, `Error: ${errText || res.statusText}`, {
11115
+ error: true
11116
+ });
11117
+ return;
11118
+ }
11119
+ const data = await res.json();
11120
+ if (disposed || slot.isStale() || requestRunId !== loadRunId || requestDbId !== currentDbId || requestTable !== currentTable || requestMode !== currentMode) {
11121
+ return;
11122
+ }
11123
+ if (!append)
11124
+ itemList.innerHTML = "";
11125
+ if (data.items.length === 0 && !append) {
11126
+ setPaneStatus(itemList, text2().dynamodb.noItems);
11127
+ } else {
11128
+ appendItems(data.items);
11129
+ }
11130
+ currentNextToken = data.lastEvaluatedKey;
11131
+ moreBtn.hidden = !data.lastEvaluatedKey;
11132
+ cumulativeShownCount += data.items.length;
11133
+ cumulativeScannedCount += data.scannedCount;
11134
+ itemStatus.textContent = `${cumulativeShownCount.toLocaleString()} shown / ${cumulativeScannedCount.toLocaleString()} scanned`;
11135
+ highlightActiveItem(currentItemKeyToken);
11136
+ } catch (err) {
11137
+ if (slot.isStale())
11138
+ return;
11139
+ setPaneStatus(itemList, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
11140
+ } finally {
11141
+ slot.finish();
11142
+ if (!slot.isStale())
11143
+ moreBtn.disabled = false;
11144
+ }
11145
+ }
11146
+ async function fetchTableInfo(table2) {
11147
+ if (disposed || !currentDbId)
11148
+ return;
11149
+ const slot = tableInfoGuard.start();
11150
+ const requestDbId = currentDbId;
11151
+ try {
11152
+ const params = new URLSearchParams({ db: requestDbId, table: table2 });
11153
+ const res = await trackLoad(fetch(`/_db/dynamodb/table?${params}`, { signal: slot.signal }));
11154
+ if (disposed || slot.isStale())
11155
+ return;
11156
+ if (!res.ok)
11157
+ return;
11158
+ const data = await res.json();
11159
+ if (disposed || slot.isStale() || requestDbId !== currentDbId || currentTable !== table2) {
11160
+ return;
11161
+ }
11162
+ currentTableInfo = data.table;
11163
+ if (!currentItemKeyToken)
11164
+ renderTableInfo();
11165
+ } catch {} finally {
11166
+ slot.finish();
11167
+ }
11168
+ }
11169
+ async function selectItemByKey(key) {
11170
+ if (disposed || !currentDbId || !currentTable)
11171
+ return;
11172
+ const slot = itemGuard.start();
11173
+ const requestRunId = ++itemRunId;
11174
+ const requestDbId = currentDbId;
11175
+ const requestTable = currentTable;
11176
+ try {
11177
+ const params = new URLSearchParams({
11178
+ db: requestDbId,
11179
+ table: requestTable,
11180
+ key: JSON.stringify(key)
11181
+ });
11182
+ const res = await trackLoad(fetch(`/_db/dynamodb/item?${params}`, { signal: slot.signal }));
11183
+ if (disposed || slot.isStale())
11184
+ return;
11185
+ if (!res.ok)
11186
+ return;
11187
+ const data = await res.json();
11188
+ if (disposed || slot.isStale() || requestRunId !== itemRunId || requestDbId !== currentDbId || requestTable !== currentTable || !data.item) {
11189
+ return;
11190
+ }
11191
+ selectItem(data.item);
11192
+ } catch {} finally {
11193
+ slot.finish();
11194
+ }
11195
+ }
11196
+ async function selectTable(name) {
11197
+ if (disposed || !currentDbId)
11198
+ return;
11199
+ currentTable = name;
11200
+ currentTableInfo = null;
11201
+ currentItemKeyToken = null;
11202
+ currentNextToken = undefined;
11203
+ highlightActiveTable(name);
11204
+ notifySelectionChange();
11205
+ setPaneEmpty(detailPane, text2().dynamodb.selectItem);
11206
+ await fetchTableInfo(name);
11207
+ await loadItems(false);
11208
+ }
11209
+ scanModeBtn.addEventListener("click", () => {
11210
+ if (currentMode === "scan")
11211
+ return;
11212
+ setMode("scan");
11213
+ notifySelectionChange();
11214
+ loadItems(false);
11215
+ });
11216
+ queryModeBtn.addEventListener("click", () => {
11217
+ if (currentMode === "query")
11218
+ return;
11219
+ setMode("query");
11220
+ notifySelectionChange();
11221
+ });
11222
+ queryForm.addEventListener("submit", (e2) => {
11223
+ e2.preventDefault();
11224
+ notifySelectionChange();
11225
+ loadItems(false);
11226
+ });
11227
+ for (const input of [keyConditionInput, filterInput]) {
11228
+ input.addEventListener("keydown", (e2) => {
11229
+ if (isImeComposing(e2))
11230
+ return;
11231
+ });
11232
+ }
11233
+ moreBtn.addEventListener("click", () => loadItems(true));
11234
+ tableMoreBtn.addEventListener("click", async () => {
11235
+ if (!currentDbId || !currentTableNextToken || disposed)
11236
+ return;
11237
+ const slot = tablePageGuard.start();
11238
+ const requestDbId = currentDbId;
11239
+ const requestToken = currentTableNextToken;
11240
+ tableMoreBtn.disabled = true;
11241
+ tableMoreBtn.title = "";
11242
+ try {
11243
+ const params = new URLSearchParams({
11244
+ db: requestDbId,
11245
+ exclusiveStartTableName: requestToken
11246
+ });
11247
+ const res = await trackLoad(fetch(`/_db/dynamodb/tables?${params}`, { signal: slot.signal }));
11248
+ if (disposed || slot.isStale() || requestDbId !== currentDbId)
11249
+ return;
11250
+ if (!res.ok) {
11251
+ tableMoreBtn.title = await res.text() || res.statusText;
11252
+ return;
11253
+ }
11254
+ const data = await res.json();
11255
+ if (disposed || slot.isStale() || requestDbId !== currentDbId)
11256
+ return;
11257
+ renderTables(data.tableNames, true);
11258
+ currentTableNextToken = data.lastEvaluatedTableName;
11259
+ tableMoreBtn.hidden = !currentTableNextToken;
11260
+ if (currentTable)
11261
+ highlightActiveTable(currentTable);
11262
+ } catch (err) {
11263
+ if (slot.isStale())
11264
+ return;
11265
+ tableMoreBtn.title = err instanceof Error ? err.message : String(err);
11266
+ } finally {
11267
+ slot.finish();
11268
+ if (!slot.isStale())
11269
+ tableMoreBtn.disabled = false;
11270
+ }
11271
+ });
11272
+ tableList.addEventListener("click", (e2) => {
11273
+ const row = e2.target?.closest(".dynamodb-table-item");
11274
+ if (!row || !tableList.contains(row))
11275
+ return;
11276
+ const name = row.dataset.tableName;
11277
+ if (name)
11278
+ selectTable(name);
11279
+ });
11280
+ itemList.addEventListener("click", (e2) => {
11281
+ const row = e2.target?.closest(".dynamodb-item-row");
11282
+ if (!row || !itemList.contains(row))
11283
+ return;
11284
+ const token = row.dataset.keyToken;
11285
+ if (!token)
11286
+ return;
11287
+ const item = itemsByKeyToken.get(token);
11288
+ if (item)
11289
+ selectItem(item, token);
11290
+ });
11291
+ async function load(dbId, initial) {
11292
+ if (disposed)
11293
+ return;
11294
+ if (currentDbId === dbId && !initial)
11295
+ return;
11296
+ tableGuard.dispose();
11297
+ tablePageGuard.dispose();
11298
+ tableInfoGuard.dispose();
11299
+ itemsGuard.dispose();
11300
+ itemGuard.dispose();
11301
+ const slot = tableGuard.start();
11302
+ const requestRunId = ++loadRunId;
11303
+ currentDbId = dbId;
11304
+ currentTable = null;
11305
+ currentTableInfo = null;
11306
+ currentTableNextToken = undefined;
11307
+ currentItemKeyToken = null;
11308
+ currentNextToken = undefined;
11309
+ setMode(initial?.mode === "query" ? "query" : "scan");
11310
+ currentScanIndexForward = initial?.scanIndexForward ?? true;
11311
+ keyConditionInput.value = initial?.keyConditionExpression ?? "";
11312
+ filterInput.value = initial?.filterExpression ?? "";
11313
+ attributeValuesInput.value = initial?.expressionAttributeValues ?? "";
11314
+ queryError.hidden = true;
11315
+ itemStatus.textContent = "";
11316
+ itemList.innerHTML = "";
11317
+ itemsByKeyToken.clear();
11318
+ itemRowsByKeyToken.clear();
11319
+ activeItemRow = null;
11320
+ moreBtn.hidden = true;
11321
+ tableMoreBtn.hidden = true;
11322
+ setPaneEmpty(detailPane, text2().dynamodb.selectItem);
11323
+ setPaneStatus(tableList, "Loading tables...");
11324
+ try {
11325
+ const res = await trackLoad(fetch(`/_db/dynamodb/tables?db=${encodeURIComponent(dbId)}`, {
11326
+ signal: slot.signal
11327
+ }));
11328
+ if (disposed || slot.isStale())
11329
+ return;
11330
+ if (!res.ok) {
11331
+ const errText = await res.text();
11332
+ setPaneStatus(tableList, `Error: ${errText || res.statusText}`, {
11333
+ error: true
11334
+ });
11335
+ return;
11336
+ }
11337
+ const data = await res.json();
11338
+ if (disposed || slot.isStale() || requestRunId !== loadRunId || currentDbId !== dbId) {
11339
+ return;
11340
+ }
11341
+ renderTables(data.tableNames);
11342
+ currentTableNextToken = data.lastEvaluatedTableName;
11343
+ tableMoreBtn.hidden = !currentTableNextToken;
11344
+ const selected = initial?.table && data.tableNames.includes(initial.table) && initial.table || data.tableNames[0] || null;
11345
+ if (!selected)
11346
+ return;
11347
+ suppressNotify = true;
11348
+ try {
11349
+ await selectTable(selected);
11350
+ if (currentDbId !== dbId)
11351
+ return;
11352
+ if (initial?.itemKey) {
11353
+ try {
11354
+ const key = JSON.parse(initial.itemKey);
11355
+ await selectItemByKey(key);
11356
+ } catch {}
11357
+ }
11358
+ } finally {
11359
+ suppressNotify = false;
11360
+ }
11361
+ notifySelectionChange();
11362
+ } catch (err) {
11363
+ if (slot.isStale())
11364
+ return;
11365
+ setPaneStatus(tableList, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
11366
+ } finally {
11367
+ slot.finish();
11368
+ }
11369
+ }
11370
+ function clear() {
11371
+ tableGuard.dispose();
11372
+ tablePageGuard.dispose();
11373
+ tableInfoGuard.dispose();
11374
+ itemsGuard.dispose();
11375
+ itemGuard.dispose();
11376
+ loadRunId++;
11377
+ itemRunId++;
11378
+ suppressNotify = false;
11379
+ currentDbId = null;
11380
+ currentTable = null;
11381
+ currentTableInfo = null;
11382
+ currentTableNextToken = undefined;
11383
+ currentItemKeyToken = null;
11384
+ currentNextToken = undefined;
11385
+ cumulativeShownCount = 0;
11386
+ cumulativeScannedCount = 0;
11387
+ setMode("scan");
11388
+ keyConditionInput.value = "";
11389
+ filterInput.value = "";
11390
+ attributeValuesInput.value = "";
11391
+ queryError.hidden = true;
11392
+ tableList.innerHTML = "";
11393
+ tableMoreBtn.hidden = true;
11394
+ tableMoreBtn.disabled = false;
11395
+ tableMoreBtn.title = "";
11396
+ activeTableRow = null;
11397
+ itemStatus.textContent = "";
11398
+ itemList.innerHTML = "";
11399
+ itemsByKeyToken.clear();
11400
+ itemRowsByKeyToken.clear();
11401
+ activeItemRow = null;
11402
+ moreBtn.hidden = true;
11403
+ setPaneEmpty(detailPane, text2().dynamodb.selectItem);
11404
+ }
11405
+ setMode("scan");
11406
+ function getSelection() {
11407
+ return {
11408
+ table: currentTable ?? undefined,
11409
+ mode: currentMode,
11410
+ keyConditionExpression: keyConditionInput.value.trim() || undefined,
11411
+ filterExpression: filterInput.value.trim() || undefined,
11412
+ expressionAttributeValues: attributeValuesInput.value.trim() || undefined,
11413
+ scanIndexForward: currentMode === "query" ? currentScanIndexForward : undefined,
11414
+ itemKey: currentItemKeyToken ?? undefined
11415
+ };
11416
+ }
11417
+ function dispose() {
11418
+ disposed = true;
11419
+ clear();
11420
+ }
11421
+ function localize() {
11422
+ const t2 = text2();
11423
+ tableListHeader.textContent = t2.dynamodb.table;
11424
+ scanModeBtn.textContent = t2.dynamodb.scanMode;
11425
+ queryModeBtn.textContent = t2.dynamodb.queryMode;
11426
+ keyConditionInput.placeholder = t2.dynamodb.keyConditionPlaceholder;
11427
+ filterInput.placeholder = t2.dynamodb.filterPlaceholder;
11428
+ attributeValuesInput.placeholder = t2.dynamodb.attributeValuesPlaceholder;
11429
+ runBtn.textContent = t2.dynamodb.runQuery;
11430
+ tableMoreBtn.textContent = t2.common.loadMore;
11431
+ moreBtn.textContent = t2.common.loadMore;
11432
+ if (!activeItemRow) {
11433
+ if (currentTableInfo)
11434
+ renderTableInfo();
11435
+ else
11436
+ setPaneEmpty(detailPane, t2.dynamodb.selectItem);
11437
+ }
11438
+ }
11439
+ return {
11440
+ el: container,
11441
+ sidebarSlot,
11442
+ load,
11443
+ clear,
11444
+ dispose,
11445
+ getSelection,
11446
+ localize
11447
+ };
11448
+ }
11449
+
10689
11450
  // web-src/views/database/elasticsearch-explorer.ts
10690
11451
  function createElasticsearchExplorer(callbacks = {}) {
10691
11452
  const text2 = () => (callbacks.getText?.() ?? dbText("en")).explorer;
@@ -16386,6 +17147,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
16386
17147
  var ROW_HEIGHT = 28;
16387
17148
  var OVERSCAN = 20;
16388
17149
  var PAGE_SIZE = 200;
17150
+ var MAX_PAGE_CACHE_PAGES = 32;
16389
17151
  var FILTER_DEBOUNCE_MS = 300;
16390
17152
  var DEFAULT_COL_WIDTH = 180;
16391
17153
  var CELL_PREVIEW_MAX_CHARS = 4000;
@@ -16520,9 +17282,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
16520
17282
  const filteredEmpty = document.createElement("div");
16521
17283
  filteredEmpty.className = "db-pane-empty";
16522
17284
  filteredEmpty.hidden = true;
16523
- const filteredEmptyIcon = document.createElement("div");
16524
- filteredEmptyIcon.className = "db-pane-empty-icon";
16525
- filteredEmptyIcon.innerHTML = iconSvg("octicon-search", SEARCH_16_PATH);
16526
17285
  const filteredEmptyTitle = document.createElement("div");
16527
17286
  filteredEmptyTitle.className = "db-pane-empty-title";
16528
17287
  const filteredEmptyHint = document.createElement("div");
@@ -16542,7 +17301,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
16542
17301
  filteredEmptyAction.hidden = true;
16543
17302
  filteredEmptyAction.disabled = true;
16544
17303
  filteredEmptyActions.append(filteredEmptyReloadAction, filteredEmptyAction);
16545
- filteredEmpty.append(filteredEmptyIcon, filteredEmptyTitle, filteredEmptyHint, filteredEmptyActions);
17304
+ filteredEmpty.append(filteredEmptyTitle, filteredEmptyHint, filteredEmptyActions);
16546
17305
  const detailPanel = document.createElement("div");
16547
17306
  detailPanel.className = "db-grid-detail-panel";
16548
17307
  detailPanel.hidden = true;
@@ -16563,13 +17322,34 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
16563
17322
  function setActiveCell(rowIndex, colIndex) {
16564
17323
  activeCellRowIndex = rowIndex;
16565
17324
  activeCellColIndex = colIndex;
16566
- for (const c2 of body.querySelectorAll(".db-grid-cell-active")) {
16567
- c2.classList.remove("db-grid-cell-active");
17325
+ if (activeCellElement?.isConnected) {
17326
+ activeCellElement.classList.remove("db-grid-cell-active");
17327
+ } else {
17328
+ for (const c2 of body.querySelectorAll(".db-grid-cell-active")) {
17329
+ c2.classList.remove("db-grid-cell-active");
17330
+ }
16568
17331
  }
17332
+ activeCellElement = null;
16569
17333
  if (rowIndex < 0 || colIndex < 0)
16570
17334
  return;
16571
17335
  const targetRow = body.children[rowIndex - renderStartRow];
16572
- targetRow?.children[colIndex + 1]?.classList.add("db-grid-cell-active");
17336
+ const targetCell = targetRow?.children[colIndex + 1];
17337
+ if (!targetCell)
17338
+ return;
17339
+ targetCell.classList.add("db-grid-cell-active");
17340
+ activeCellElement = targetCell;
17341
+ }
17342
+ function setSelectedRow(rowIndex, row) {
17343
+ selectedRowIndex = rowIndex;
17344
+ if (selectedRowElement?.isConnected) {
17345
+ selectedRowElement.classList.remove("selected");
17346
+ } else {
17347
+ body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
17348
+ r2.classList.remove("selected");
17349
+ });
17350
+ }
17351
+ selectedRowElement = row;
17352
+ row?.classList.add("selected");
16573
17353
  }
16574
17354
  function clearActiveCell() {
16575
17355
  setActiveCell(-1, -1);
@@ -16631,8 +17411,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
16631
17411
  let isRefreshing = false;
16632
17412
  let filterTimer = null;
16633
17413
  let selectedRowIndex = -1;
17414
+ let selectedRowElement = null;
16634
17415
  let activeCellRowIndex = -1;
16635
17416
  let activeCellColIndex = -1;
17417
+ let activeCellElement = null;
16636
17418
  let renderStartRow = 0;
16637
17419
  const fkColumns = new Set;
16638
17420
  let editMode = false;
@@ -16815,6 +17597,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
16815
17597
  }
16816
17598
  function resetSelectionAndDetail() {
16817
17599
  selectedRowIndex = -1;
17600
+ selectedRowElement = null;
16818
17601
  detailPanel.hidden = true;
16819
17602
  clearDetailContent();
16820
17603
  }
@@ -16910,6 +17693,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
16910
17693
  statusEl?.remove();
16911
17694
  statusEl = null;
16912
17695
  selectedRowIndex = -1;
17696
+ selectedRowElement = null;
17697
+ activeCellElement = null;
16913
17698
  closeExportMenu();
16914
17699
  hideRelatedPanel();
16915
17700
  detailPanel.hidden = true;
@@ -17433,7 +18218,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17433
18218
  renderViewport();
17434
18219
  }
17435
18220
  function ensurePage(pageStart) {
17436
- if (pageCache.has(pageStart))
18221
+ if (getCachedPage(pageStart) !== undefined)
17437
18222
  return Promise.resolve();
17438
18223
  const pending = pendingPages.get(pageStart);
17439
18224
  if (pending)
@@ -17444,7 +18229,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17444
18229
  const promise = callbacks.fetchPage(currentTable, pageStart, PAGE_SIZE, sort, filters, signal).then((data) => {
17445
18230
  if (gen !== loadGeneration)
17446
18231
  return;
17447
- pageCache.set(pageStart, data.rows);
18232
+ rememberPage(pageStart, data.rows);
17448
18233
  totalRows = data.totalRows;
17449
18234
  syncSpacer();
17450
18235
  updateStatus();
@@ -17464,6 +18249,24 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17464
18249
  pendingPages.set(pageStart, tracked);
17465
18250
  return tracked;
17466
18251
  }
18252
+ function rememberPage(pageStart, rows) {
18253
+ pageCache.delete(pageStart);
18254
+ pageCache.set(pageStart, rows);
18255
+ while (pageCache.size > MAX_PAGE_CACHE_PAGES) {
18256
+ const oldest = pageCache.keys().next().value;
18257
+ if (oldest === undefined)
18258
+ break;
18259
+ pageCache.delete(oldest);
18260
+ }
18261
+ }
18262
+ function getCachedPage(pageStart) {
18263
+ const rows = pageCache.get(pageStart);
18264
+ if (rows === undefined)
18265
+ return;
18266
+ pageCache.delete(pageStart);
18267
+ pageCache.set(pageStart, rows);
18268
+ return rows;
18269
+ }
17467
18270
  function buildEditableCell(colIndex, value, opts) {
17468
18271
  const cell = document.createElement("div");
17469
18272
  cell.className = "db-grid-cell db-grid-cell-edit";
@@ -17545,14 +18348,16 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17545
18348
  }
17546
18349
  function buildDataRow(i2) {
17547
18350
  const pageStart = Math.floor(i2 / PAGE_SIZE) * PAGE_SIZE;
17548
- const pageRows = pageCache.get(pageStart);
18351
+ const pageRows = getCachedPage(pageStart);
17549
18352
  const rowData = pageRows ? pageRows[i2 - pageStart] : null;
17550
18353
  const row = document.createElement("div");
17551
18354
  row.className = "db-grid-row";
17552
18355
  if (i2 % 2 === 1)
17553
18356
  row.classList.add("alt");
17554
- if (i2 === selectedRowIndex)
18357
+ if (i2 === selectedRowIndex) {
17555
18358
  row.classList.add("selected");
18359
+ selectedRowElement = row;
18360
+ }
17556
18361
  const rowIndex = i2;
17557
18362
  const rowNum = document.createElement("div");
17558
18363
  rowNum.className = "db-grid-cell db-grid-rownum";
@@ -17601,11 +18406,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17601
18406
  const fkClickable = fkColumns.has(cellColName) && (!embedded || !!callbacks.onForeignKeyCellClick);
17602
18407
  const isEditingThisCell = !ro && i2 === editingCellRow && c2 === editingCellCol;
17603
18408
  const activate = () => {
17604
- selectedRowIndex = rowIndex;
17605
- body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
17606
- r2.classList.remove("selected");
17607
- });
17608
- row.classList.add("selected");
18409
+ setSelectedRow(rowIndex, row);
17609
18410
  setActiveCell(rowIndex, cellColIndex);
17610
18411
  if (fkClickable) {
17611
18412
  if (embedded) {
@@ -17656,17 +18457,14 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17656
18457
  }
17657
18458
  if (!isEditingThisCell && i2 === activeCellRowIndex && c2 === activeCellColIndex) {
17658
18459
  cellEl.classList.add("db-grid-cell-active");
18460
+ activeCellElement = cellEl;
17659
18461
  }
17660
18462
  row.appendChild(cellEl);
17661
18463
  }
17662
18464
  return row;
17663
18465
  }
17664
18466
  row.addEventListener("click", () => {
17665
- selectedRowIndex = rowIndex;
17666
- body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
17667
- r2.classList.remove("selected");
17668
- });
17669
- row.classList.add("selected");
18467
+ setSelectedRow(rowIndex, row);
17670
18468
  });
17671
18469
  for (let c2 = 0;c2 < columnNames.length; c2++) {
17672
18470
  const cell = document.createElement("div");
@@ -17691,11 +18489,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17691
18489
  }
17692
18490
  cell.addEventListener("click", (e2) => {
17693
18491
  e2.stopPropagation();
17694
- selectedRowIndex = rowIndex;
17695
- body.querySelectorAll(".db-grid-row.selected").forEach((r2) => {
17696
- r2.classList.remove("selected");
17697
- });
17698
- row.classList.add("selected");
18492
+ setSelectedRow(rowIndex, row);
17699
18493
  setActiveCell(rowIndex, cellColIndex);
17700
18494
  if (fkClickable) {
17701
18495
  if (embedded) {
@@ -17709,6 +18503,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17709
18503
  });
17710
18504
  if (i2 === activeCellRowIndex && c2 === activeCellColIndex) {
17711
18505
  cell.classList.add("db-grid-cell-active");
18506
+ activeCellElement = cell;
17712
18507
  }
17713
18508
  row.appendChild(cell);
17714
18509
  }
@@ -17780,6 +18575,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17780
18575
  };
17781
18576
  }
17782
18577
  body.innerHTML = "";
18578
+ selectedRowElement = null;
18579
+ activeCellElement = null;
17783
18580
  body.style.transform = `translateY(${startRow * ROW_HEIGHT}px)`;
17784
18581
  for (let i2 = startRow;i2 < endRow; i2++) {
17785
18582
  body.appendChild(i2 < totalRows ? buildDataRow(i2) : buildDraftRow(i2 - totalRows));
@@ -17861,7 +18658,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17861
18658
  columns = initialData.columns;
17862
18659
  columnNames = columns.map((c2) => c2.name);
17863
18660
  totalRows = initialData.totalRows;
17864
- pageCache.set(0, initialData.rows);
18661
+ rememberPage(0, initialData.rows);
17865
18662
  } else {
17866
18663
  columns = [];
17867
18664
  columnNames = [];
@@ -18708,7 +19505,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
18708
19505
  snapshotHidden: !sqlMode || tab !== "snapshot",
18709
19506
  redisHidden: kind !== "redis",
18710
19507
  esHidden: kind !== "elasticsearch",
18711
- s3Hidden: kind !== "s3"
19508
+ s3Hidden: kind !== "s3",
19509
+ dynamodbHidden: kind !== "dynamodb"
18712
19510
  };
18713
19511
  }
18714
19512
  function normalizeViewForDb(view, db) {
@@ -19090,7 +19888,14 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19090
19888
  });
19091
19889
  s3Explorer.el.hidden = true;
19092
19890
  s3Explorer.sidebarSlot.hidden = true;
19093
- explorerSidebarHost.append(redisExplorer.sidebarSlot, esExplorer.sidebarSlot, s3Explorer.sidebarSlot);
19891
+ const dynamodbExplorer = createDynamoDbExplorer({
19892
+ onSelectionChange: () => cb.onStateChange(),
19893
+ getText: () => paneText(),
19894
+ trackLoad: (p2) => deps.trackLoad(p2)
19895
+ });
19896
+ dynamodbExplorer.el.hidden = true;
19897
+ dynamodbExplorer.sidebarSlot.hidden = true;
19898
+ explorerSidebarHost.append(redisExplorer.sidebarSlot, esExplorer.sidebarSlot, s3Explorer.sidebarSlot, dynamodbExplorer.sidebarSlot);
19094
19899
  const noDatastoresPane = document.createElement("div");
19095
19900
  noDatastoresPane.className = "db-no-datastores";
19096
19901
  noDatastoresPane.hidden = true;
@@ -19114,7 +19919,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19114
19919
  }
19115
19920
  const mainContent = document.createElement("div");
19116
19921
  mainContent.className = "db-main-content";
19117
- mainContent.append(tabBar, grid.el, queryEditor.el, schemaView.el, erDiagram.el, globalSearchView.el, snapshotView.el, redisExplorer.el, esExplorer.el, s3Explorer.el, noDatastoresPane);
19922
+ mainContent.append(tabBar, grid.el, queryEditor.el, schemaView.el, erDiagram.el, globalSearchView.el, snapshotView.el, redisExplorer.el, esExplorer.el, s3Explorer.el, dynamodbExplorer.el, noDatastoresPane);
19118
19923
  queryEditor.el.hidden = true;
19119
19924
  globalSearchView.el.hidden = true;
19120
19925
  snapshotView.el.hidden = true;
@@ -19242,10 +20047,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19242
20047
  redisExplorer.el.hidden = visibility.redisHidden;
19243
20048
  esExplorer.el.hidden = visibility.esHidden;
19244
20049
  s3Explorer.el.hidden = visibility.s3Hidden;
20050
+ dynamodbExplorer.el.hidden = visibility.dynamodbHidden;
19245
20051
  redisExplorer.sidebarSlot.hidden = visibility.redisHidden;
19246
20052
  esExplorer.sidebarSlot.hidden = visibility.esHidden;
19247
20053
  s3Explorer.sidebarSlot.hidden = visibility.s3Hidden;
19248
- explorerSidebarHost.hidden = visibility.redisHidden && visibility.esHidden && visibility.s3Hidden;
20054
+ dynamodbExplorer.sidebarSlot.hidden = visibility.dynamodbHidden;
20055
+ explorerSidebarHost.hidden = visibility.redisHidden && visibility.esHidden && visibility.s3Hidden && visibility.dynamodbHidden;
19249
20056
  if (noDatastoresAvailable) {
19250
20057
  toolsSection.hidden = true;
19251
20058
  prefsBar.hidden = true;
@@ -19263,9 +20070,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19263
20070
  redisExplorer.el.hidden = true;
19264
20071
  esExplorer.el.hidden = true;
19265
20072
  s3Explorer.el.hidden = true;
20073
+ dynamodbExplorer.el.hidden = true;
19266
20074
  redisExplorer.sidebarSlot.hidden = true;
19267
20075
  esExplorer.sidebarSlot.hidden = true;
19268
20076
  s3Explorer.sidebarSlot.hidden = true;
20077
+ dynamodbExplorer.sidebarSlot.hidden = true;
19269
20078
  explorerSidebarHost.hidden = true;
19270
20079
  }
19271
20080
  if (!sqlMode) {
@@ -19618,7 +20427,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19618
20427
  return;
19619
20428
  tableSelectGuard.dispose();
19620
20429
  currentTable = null;
19621
- if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
20430
+ if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3" || currentDbInfo?.kind === "dynamodb") {
19622
20431
  currentSchema = null;
19623
20432
  renderSchemaOptions([], null);
19624
20433
  currentTab = "data";
@@ -19631,15 +20440,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19631
20440
  if (currentDbInfo.kind === "redis") {
19632
20441
  esExplorer.clear();
19633
20442
  s3Explorer.clear();
20443
+ dynamodbExplorer.clear();
19634
20444
  await redisExplorer.load(dbId, explorerInitial?.redis);
19635
20445
  } else if (currentDbInfo.kind === "elasticsearch") {
19636
20446
  redisExplorer.clear();
19637
20447
  s3Explorer.clear();
20448
+ dynamodbExplorer.clear();
19638
20449
  await esExplorer.load(dbId, explorerInitial?.es);
19639
- } else {
20450
+ } else if (currentDbInfo.kind === "s3") {
19640
20451
  redisExplorer.clear();
19641
20452
  esExplorer.clear();
20453
+ dynamodbExplorer.clear();
19642
20454
  await s3Explorer.load(dbId, explorerInitial?.s3);
20455
+ } else {
20456
+ redisExplorer.clear();
20457
+ esExplorer.clear();
20458
+ s3Explorer.clear();
20459
+ await dynamodbExplorer.load(dbId, explorerInitial?.dynamodb);
19643
20460
  }
19644
20461
  if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
19645
20462
  return;
@@ -19649,6 +20466,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19649
20466
  redisExplorer.clear();
19650
20467
  esExplorer.clear();
19651
20468
  s3Explorer.clear();
20469
+ dynamodbExplorer.clear();
19652
20470
  applyVisibility();
19653
20471
  tableList.render([]);
19654
20472
  setTableListStatus(paneText().nav.loadingSchema);
@@ -19978,6 +20796,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19978
20796
  let pendingRedisInitial = initial.redis;
19979
20797
  let pendingEsInitial = initial.es;
19980
20798
  let pendingS3Initial = initial.s3;
20799
+ let pendingDynamodbInitial = initial.dynamodb;
19981
20800
  async function enter(db, schema, table2, view, options = {}) {
19982
20801
  const generation = ++loadGeneration;
19983
20802
  const filesResponse = await fetchDbFiles();
@@ -20010,6 +20829,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20010
20829
  redisExplorer.clear();
20011
20830
  esExplorer.clear();
20012
20831
  s3Explorer.clear();
20832
+ dynamodbExplorer.clear();
20013
20833
  renderNoDatastoresEmpty();
20014
20834
  applyVisibility();
20015
20835
  cb.onStateChange();
@@ -20045,6 +20865,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20045
20865
  redisExplorer.clear();
20046
20866
  esExplorer.clear();
20047
20867
  s3Explorer.clear();
20868
+ dynamodbExplorer.clear();
20048
20869
  setActiveTab("data", false);
20049
20870
  cb.onStateChange();
20050
20871
  return;
@@ -20055,15 +20876,17 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20055
20876
  const explorerInitial = {
20056
20877
  redis: pendingRedisInitial,
20057
20878
  es: pendingEsInitial,
20058
- s3: pendingS3Initial
20879
+ s3: pendingS3Initial,
20880
+ dynamodb: pendingDynamodbInitial
20059
20881
  };
20060
20882
  pendingRedisInitial = undefined;
20061
20883
  pendingEsInitial = undefined;
20062
20884
  pendingS3Initial = undefined;
20885
+ pendingDynamodbInitial = undefined;
20063
20886
  await selectDb(target, explorerInitial, generation, schema !== undefined ? schema : currentSchema, table2, view);
20064
20887
  if (generation !== loadGeneration || currentDbInfo?.id !== target)
20065
20888
  return;
20066
- if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
20889
+ if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3" || currentDbInfo?.kind === "dynamodb") {
20067
20890
  cb.onStateChange();
20068
20891
  return;
20069
20892
  }
@@ -20168,6 +20991,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20168
20991
  state.es = initial.es;
20169
20992
  if (initial.s3)
20170
20993
  state.s3 = initial.s3;
20994
+ if (initial.dynamodb)
20995
+ state.dynamodb = initial.dynamodb;
20171
20996
  } else if (currentDbInfo?.kind === "redis") {
20172
20997
  const sel = redisExplorer.getSelection();
20173
20998
  if (sel.dbIndex !== undefined || sel.key !== undefined || sel.keyFilter !== undefined) {
@@ -20183,6 +21008,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20183
21008
  if (sel.bucket !== undefined || sel.prefix !== undefined || sel.query !== undefined || sel.key !== undefined || sel.mode !== "prefix" || sel.sort !== "updated-desc" || sel.view === "explorer") {
20184
21009
  state.s3 = sel;
20185
21010
  }
21011
+ } else if (currentDbInfo?.kind === "dynamodb") {
21012
+ const sel = dynamodbExplorer.getSelection();
21013
+ if (sel.table !== undefined || sel.mode !== "scan" || sel.keyConditionExpression !== undefined || sel.filterExpression !== undefined || sel.expressionAttributeValues !== undefined || sel.itemKey !== undefined) {
21014
+ state.dynamodb = sel;
21015
+ }
20186
21016
  }
20187
21017
  return state;
20188
21018
  }
@@ -20239,6 +21069,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20239
21069
  redisExplorer.dispose();
20240
21070
  esExplorer.dispose();
20241
21071
  s3Explorer.dispose();
21072
+ dynamodbExplorer.dispose();
20242
21073
  currentDbInfo = null;
20243
21074
  currentSchema = null;
20244
21075
  currentTable = null;
@@ -20259,6 +21090,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20259
21090
  redisExplorer.localize();
20260
21091
  esExplorer.localize();
20261
21092
  s3Explorer.localize();
21093
+ dynamodbExplorer.localize();
20262
21094
  }
20263
21095
  return {
20264
21096
  el: container,
@@ -20671,7 +21503,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20671
21503
  view: tab.view,
20672
21504
  redis: tab.redis ?? null,
20673
21505
  es: tab.es ?? null,
20674
- s3: tab.s3 ?? null
21506
+ s3: tab.s3 ?? null,
21507
+ dynamodb: tab.dynamodb ?? null
20675
21508
  });
20676
21509
  if (seen.has(key))
20677
21510
  continue;
@@ -20929,7 +21762,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20929
21762
  detailPanelHeight: initial?.detailPanelHeight,
20930
21763
  redis: initial?.redis,
20931
21764
  es: initial?.es,
20932
- s3: initial?.s3
21765
+ s3: initial?.s3,
21766
+ dynamodb: initial?.dynamodb
20933
21767
  });
20934
21768
  pane.el.hidden = true;
20935
21769
  tabsList.appendChild(chip);
@@ -21620,7 +22454,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
21620
22454
  const card = document.querySelector(diffCardSelector(path));
21621
22455
  if (!card?.classList.contains("pending"))
21622
22456
  return;
21623
- const f2 = STATE.files.find((x) => x.path === path);
22457
+ const f2 = card._file || STATE.files.find((x) => x.path === path);
21624
22458
  if (!f2)
21625
22459
  return;
21626
22460
  enqueueLoad(f2, card, 5);
@@ -21692,7 +22526,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
21692
22526
  };
21693
22527
  window.addEventListener("scrollend", onEnd, { once: true });
21694
22528
  if (card.classList.contains("pending")) {
21695
- const f2 = STATE.files.find((x) => x.path === path);
22529
+ const f2 = card._file || STATE.files.find((x) => x.path === path);
21696
22530
  if (f2)
21697
22531
  enqueueLoad(f2, card, 10);
21698
22532
  }
@@ -21761,7 +22595,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
21761
22595
  const card = entry.target;
21762
22596
  if (card.classList.contains("loaded") || card.classList.contains("loading"))
21763
22597
  continue;
21764
- const f2 = STATE.files.find((x) => x.path === card.dataset.path);
22598
+ const f2 = card._file || STATE.files.find((x) => x.path === card.dataset.path);
21765
22599
  if (f2)
21766
22600
  enqueueLoad(f2, card, 0);
21767
22601
  }
@@ -21829,7 +22663,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
21829
22663
  delete card.dataset.manualMode;
21830
22664
  card.style.minHeight = `${file.estimated_height_px || 80}px`;
21831
22665
  card._diffData = null;
21832
- card._file = null;
22666
+ card._file = file;
21833
22667
  }
21834
22668
  function renderShell(meta, changedPaths) {
21835
22669
  const newFiles = meta.files || [];
@@ -21995,6 +22829,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
21995
22829
  card.dataset.key = f2.key || f2.path;
21996
22830
  card.dataset.sizeClass = f2.size_class || "small";
21997
22831
  card.dataset.status = f2.status || "M";
22832
+ card._file = f2;
21998
22833
  card.classList.toggle("viewed", STATE.viewedFiles.has(f2.path));
21999
22834
  if (f2.estimated_height_px) {
22000
22835
  card.style.minHeight = `${f2.estimated_height_px}px`;
@@ -22018,7 +22853,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22018
22853
  const card = entry.target;
22019
22854
  if (card.classList.contains("loaded") || card.classList.contains("loading"))
22020
22855
  return;
22021
- const f2 = STATE.files.find((x) => x.path === card.dataset.path);
22856
+ const f2 = card._file || STATE.files.find((x) => x.path === card.dataset.path);
22022
22857
  if (!f2)
22023
22858
  return;
22024
22859
  enqueueLoad(f2, card, 0);
@@ -22034,7 +22869,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22034
22869
  const rect = card.getBoundingClientRect();
22035
22870
  if (rect.top > viewportBottom)
22036
22871
  return;
22037
- const f2 = STATE.files.find((x) => x.path === card.dataset.path);
22872
+ const f2 = card._file || STATE.files.find((x) => x.path === card.dataset.path);
22038
22873
  if (f2)
22039
22874
  enqueueLoad(f2, card, 0);
22040
22875
  });
@@ -22182,11 +23017,15 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22182
23017
  card.classList.add("pending");
22183
23018
  if (indicator)
22184
23019
  indicator.hidden = true;
22185
- const fresh = STATE.files.find((x) => x.path === card.dataset.path);
23020
+ const fresh = card._file || STATE.files.find((x) => x.path === card.dataset.path);
22186
23021
  if (fresh && card.isConnected)
22187
23022
  enqueueLoad(fresh, card, 0);
22188
23023
  };
22189
- return trackLoad(fetch(url).then((r2) => r2.json())).then(async (data) => {
23024
+ return trackLoad(fetch(url).then(async (r2) => {
23025
+ if (!r2.ok)
23026
+ throw new Error(await r2.text());
23027
+ return r2.json();
23028
+ })).then(async (data) => {
22190
23029
  if (String(myReq) !== card.dataset.reqId)
22191
23030
  return;
22192
23031
  if (myGen !== getServerGeneration()) {
@@ -22227,7 +23066,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22227
23066
  return false;
22228
23067
  if (card.classList.contains("loaded"))
22229
23068
  return true;
22230
- const file = STATE.files.find((x) => x.path === path);
23069
+ const file = card._file || STATE.files.find((x) => x.path === path);
22231
23070
  if (!file)
22232
23071
  return false;
22233
23072
  await loadFile(file, card, undefined, { immediate: true });
@@ -22253,17 +23092,13 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22253
23092
  matching: "lines",
22254
23093
  outputFormat: layout,
22255
23094
  synchronisedScroll: true,
22256
- highlight: !!(STATE.syntaxHighlight && file.highlight && hljsRef),
23095
+ highlight: false,
22257
23096
  fileListToggle: false,
22258
23097
  fileContentToggle: false
22259
23098
  }, hljsRef);
22260
23099
  ui.draw();
22261
23100
  if (STATE.ignoreWs)
22262
23101
  suppressWhitespaceOnlyInlineHighlights(body);
22263
- if (STATE.syntaxHighlight && file.highlight && hljsRef && typeof ui.highlightCode === "function") {
22264
- ui.highlightCode();
22265
- highlightPlaintextSpans(card, file);
22266
- }
22267
23102
  enhanceMediaCard(file, card);
22268
23103
  syncSideScrollCard(card);
22269
23104
  appendStatSquaresToHeader(card, file);
@@ -22592,10 +23427,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22592
23427
  const spans = card.querySelectorAll("tr.gdp-inserted-ctx .d2h-code-line-ctn:not([data-gdp-hl])");
22593
23428
  highlightDiffSpans(file, spans);
22594
23429
  }
22595
- function highlightPlaintextSpans(card, file) {
22596
- const spans = card.querySelectorAll(".d2h-code-line-ctn.hljs.plaintext:not([data-gdp-hl])");
22597
- highlightDiffSpans(file, spans);
22598
- }
22599
23430
  function highlightDiffSpans(file, spans, deadline) {
22600
23431
  if (file.size_class === "huge")
22601
23432
  return true;
@@ -22626,8 +23457,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22626
23457
  return true;
22627
23458
  }
22628
23459
  function scheduleIdleHighlight(card, file) {
22629
- if (file.highlight)
22630
- return;
22631
23460
  if (file.size_class === "huge")
22632
23461
  return;
22633
23462
  if (!STATE.syntaxHighlight)
@@ -22683,14 +23512,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22683
23512
  for (const w of cards) {
22684
23513
  const r2 = w.getBoundingClientRect();
22685
23514
  if (r2.top <= scanY && r2.bottom > scanY) {
22686
- const text2 = w.dataset.path || "";
22687
- let best = null, bestLen = 0;
22688
- STATE.files.forEach((f2) => {
22689
- if ((text2 === f2.path || text2.endsWith(f2.path)) && f2.path.length > bestLen) {
22690
- best = f2.path;
22691
- bestLen = f2.path.length;
22692
- }
22693
- });
23515
+ const best = w.dataset.path || "";
22694
23516
  if (best) {
22695
23517
  markActive(best);
22696
23518
  const recentlyTouched = performance.now() - (window.__gdpSidebarTouchedAt || 0) < 1500;
@@ -23275,6 +24097,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23275
24097
  let generation = 0;
23276
24098
  let selectionGeneration = 0;
23277
24099
  let selectedSha = "";
24100
+ let activeHistoryRow = null;
23278
24101
  let query = "";
23279
24102
  let mode = "history";
23280
24103
  let routeRef = "HEAD";
@@ -23387,6 +24210,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23387
24210
  return absolute;
23388
24211
  return `${relative} (${absolute})`;
23389
24212
  }
24213
+ function historyItemSelector(sha) {
24214
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(sha) : sha.replace(/["\\]/g, "\\$&");
24215
+ return `.history-item[data-sha="${escaped}"]`;
24216
+ }
23390
24217
  function fetchPage(skip, requestGeneration) {
23391
24218
  const params = new URLSearchParams;
23392
24219
  params.set("ref", ref);
@@ -23440,6 +24267,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23440
24267
  html.push(commitRow(commit));
23441
24268
  }
23442
24269
  list2.innerHTML = html.join("");
24270
+ activeHistoryRow = list2.querySelector(".history-item.active");
23443
24271
  syncRefreshStatusText();
23444
24272
  }
23445
24273
  function syncRefreshButton(button) {
@@ -23527,9 +24355,15 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23527
24355
  info.hidden = false;
23528
24356
  }
23529
24357
  function updateActiveRow() {
23530
- list2.querySelectorAll(".history-item").forEach((row) => {
23531
- row.classList.toggle("active", row.dataset.sha === selectedSha);
23532
- });
24358
+ if (activeHistoryRow && (!("isConnected" in activeHistoryRow) || activeHistoryRow.isConnected)) {
24359
+ activeHistoryRow.classList.remove("active");
24360
+ } else {
24361
+ list2.querySelectorAll(".history-item.active").forEach((row) => {
24362
+ row.classList.remove("active");
24363
+ });
24364
+ }
24365
+ activeHistoryRow = selectedSha ? list2.querySelector(historyItemSelector(selectedSha)) : null;
24366
+ activeHistoryRow?.classList.add("active");
23533
24367
  }
23534
24368
  function isEditableTarget(target) {
23535
24369
  return typeof Element === "function" && target instanceof Element && !!target.closest('input, textarea, select, button, [contenteditable="true"]');
@@ -23720,8 +24554,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23720
24554
  scrollToSelected();
23721
24555
  }
23722
24556
  function scrollToSelected() {
23723
- const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(selectedSha) : selectedSha.replace(/["\\]/g, "\\$&");
23724
- list2.querySelector(`.history-item[data-sha="${escaped}"]`)?.scrollIntoView({ block: "center" });
24557
+ list2.querySelector(historyItemSelector(selectedSha))?.scrollIntoView({ block: "center" });
23725
24558
  }
23726
24559
  let entering = Promise.resolve();
23727
24560
  function enterHistory(options = {}) {
@@ -23797,6 +24630,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23797
24630
  generation++;
23798
24631
  loading = false;
23799
24632
  inFlight = null;
24633
+ if (filterTimer) {
24634
+ clearTimeout(filterTimer);
24635
+ filterTimer = undefined;
24636
+ }
23800
24637
  selectionGeneration++;
23801
24638
  selectedSha = "";
23802
24639
  setBanner("");
@@ -24697,7 +25534,7 @@ code-viewer annotate add-db --db app.db --tab query \\
24697
25534
  database: {
24698
25535
  nav: "Datastores",
24699
25536
  title: "Datastore Viewer",
24700
- intro: "Browse and edit SQLite files, Docker-hosted databases, Redis, Elasticsearch, and S3-compatible object stores from one local viewer.",
25537
+ intro: "Browse SQLite files, Docker-hosted databases, Redis, Elasticsearch, DynamoDB, and S3-compatible object stores from one local viewer.",
24701
25538
  groups: [
24702
25539
  {
24703
25540
  title: "Supported datastores",
@@ -24725,6 +25562,10 @@ code-viewer annotate add-db --db app.db --tab query \\
24725
25562
  "Elasticsearch",
24726
25563
  "Detected from compose files. List indices, view mappings, paginate with search_after, run lucene q= or DSL queries on an allowlist. Edit / create / delete documents with _seq_no / _primary_term optimistic concurrency. Take snapshots/diffs."
24727
25564
  ],
25565
+ [
25566
+ "DynamoDB / LocalStack",
25567
+ "Detected when DynamoDB is enabled on a LocalStack compose service. List tables, inspect key schemas, scan or query items, follow pagination tokens, and open item details with a copyable key. Browsing is read-only."
25568
+ ],
24728
25569
  [
24729
25570
  "S3 / MinIO / LocalStack",
24730
25571
  "Detected from compose files. Folder-tree browse, prefix/filename search, updated-time sort, and previews for images, video, audio, PDF, Markdown, HTML, and text. Edit text/markdown/JSON object bodies inline, upload new objects, and delete existing ones. LocalStack falls back to `docker exec curl` when no host port is published; MinIO requires a published host port."
@@ -24781,7 +25622,7 @@ code-viewer annotate add-db --db app.db --tab query \\
24781
25622
  ],
24782
25623
  [
24783
25624
  "Datastore explorers",
24784
- "Redis / Elasticsearch / S3 keep the same Multi-DB tab UI but swap the table tree for a key-space / index-tree / folder-tree explorer. Each supports value editing, document/key/object creation, and deletion via in-pane editors and confirmation dialogs."
25625
+ "Redis / Elasticsearch / DynamoDB / S3 keep the same Multi-DB tab UI but swap the table tree for a key-space / index-tree / table-list / folder-tree explorer. Redis, Elasticsearch, and S3 provide editing or creation flows; DynamoDB browsing is read-only."
24785
25626
  ]
24786
25627
  ]
24787
25628
  }
@@ -25356,7 +26197,7 @@ code-viewer annotate add-db --db app.db --tab query \\
25356
26197
  database: {
25357
26198
  nav: "データストア",
25358
26199
  title: "データストアビューア",
25359
- intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、S3 互換オブジェクトストアをローカルビューアで閲覧・編集できます。",
26200
+ intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、DynamoDB、S3 互換オブジェクトストアをローカルビューアで閲覧できます。",
25360
26201
  groups: [
25361
26202
  {
25362
26203
  title: "対応データストア",
@@ -25384,6 +26225,10 @@ code-viewer annotate add-db --db app.db --tab query \\
25384
26225
  "Elasticsearch",
25385
26226
  "compose ファイルから検出。インデックス一覧、マッピング、search_after ページング、lucene q= と許可リスト経由の DSL、スナップショット/差分に対応。_seq_no / _primary_term 楽観ロックでドキュメントの編集 / 新規作成 / 削除も可能。"
25386
26227
  ],
26228
+ [
26229
+ "DynamoDB / LocalStack",
26230
+ "LocalStack の compose サービスで DynamoDB が有効な場合に検出。テーブル一覧、キースキーマ、scan / query、継続トークンによるページング、コピー可能なキー付きのアイテム詳細を表示します。閲覧専用です。"
26231
+ ],
25387
26232
  [
25388
26233
  "S3 / MinIO / LocalStack",
25389
26234
  "compose ファイルから検出。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
@@ -25440,7 +26285,7 @@ code-viewer annotate add-db --db app.db --tab query \\
25440
26285
  ],
25441
26286
  [
25442
26287
  "データストア専用エクスプローラ",
25443
- "Redis / Elasticsearch / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー / フォルダツリーに差し替えます。それぞれインライン編集ペインと確認ダイアログによる値編集 / ドキュメント・キー・オブジェクト作成 / 削除に対応。"
26288
+ "Redis / Elasticsearch / DynamoDB / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー / テーブル一覧 / フォルダツリーに差し替えます。Redis、Elasticsearch、S3 は編集・作成フローを提供し、DynamoDB は閲覧専用です。"
25444
26289
  ]
25445
26290
  ]
25446
26291
  }
@@ -31385,11 +32230,106 @@ code-viewer query agent-help`
31385
32230
  const match2 = computeFuzzyMatch(query, path);
31386
32231
  return match2 ? { score: match2.score, ranges: match2.ranges } : null;
31387
32232
  }
31388
- function rankFuzzyPaths(query, items) {
31389
- return items.map((item) => {
32233
+ function rankFuzzyPaths(query, items, limit) {
32234
+ const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
32235
+ const compare = (a2, b2) => b2.tier - a2.tier || b2.score - a2.score || a2.item.path.localeCompare(b2.item.path);
32236
+ if (!bounded) {
32237
+ return items.map((item) => {
32238
+ const match2 = computeFuzzyMatch(query, item.path);
32239
+ return match2 ? { item, score: match2.score, ranges: match2.ranges, tier: match2.tier } : null;
32240
+ }).filter((item) => item !== null).sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
32241
+ }
32242
+ const top = [];
32243
+ for (const item of items) {
31390
32244
  const match2 = computeFuzzyMatch(query, item.path);
31391
- return match2 ? { item, score: match2.score, ranges: match2.ranges, tier: match2.tier } : null;
31392
- }).filter((item) => item !== null).sort((a2, b2) => b2.tier - a2.tier || b2.score - a2.score || a2.item.path.localeCompare(b2.item.path)).map(({ item, score, ranges }) => ({ item, score, ranges }));
32245
+ if (!match2)
32246
+ continue;
32247
+ const ranked = {
32248
+ item,
32249
+ score: match2.score,
32250
+ ranges: match2.ranges,
32251
+ tier: match2.tier
32252
+ };
32253
+ pushBoundedTop(top, ranked, bounded, compare);
32254
+ }
32255
+ return top.sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
32256
+ }
32257
+ function pushBoundedTop(heap, item, limit, compareBestFirst) {
32258
+ const isWorse = (a2, b2) => compareBestFirst(a2, b2) > 0;
32259
+ const siftUp = (index) => {
32260
+ while (index > 0) {
32261
+ const parent = Math.floor((index - 1) / 2);
32262
+ if (!isWorse(heap[index], heap[parent]))
32263
+ break;
32264
+ [heap[index], heap[parent]] = [heap[parent], heap[index]];
32265
+ index = parent;
32266
+ }
32267
+ };
32268
+ const siftDown = (index) => {
32269
+ while (true) {
32270
+ const left = index * 2 + 1;
32271
+ const right = left + 1;
32272
+ let worst = index;
32273
+ if (left < heap.length && isWorse(heap[left], heap[worst]))
32274
+ worst = left;
32275
+ if (right < heap.length && isWorse(heap[right], heap[worst]))
32276
+ worst = right;
32277
+ if (worst === index)
32278
+ break;
32279
+ [heap[index], heap[worst]] = [heap[worst], heap[index]];
32280
+ index = worst;
32281
+ }
32282
+ };
32283
+ if (heap.length < limit) {
32284
+ heap.push(item);
32285
+ siftUp(heap.length - 1);
32286
+ return;
32287
+ }
32288
+ if (compareBestFirst(item, heap[0]) >= 0)
32289
+ return;
32290
+ heap[0] = item;
32291
+ siftDown(0);
32292
+ }
32293
+ function rankGlobPathMatches(query, items, limit) {
32294
+ const matchPath = createGlobPathMatcher(query);
32295
+ if (!matchPath)
32296
+ return [];
32297
+ const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
32298
+ const compare = (a2, b2) => b2.score - a2.score || a2.item.path.localeCompare(b2.item.path);
32299
+ if (!bounded) {
32300
+ return items.map((item) => {
32301
+ const match2 = matchPath(item.path);
32302
+ return match2 ? {
32303
+ item,
32304
+ score: match2.score,
32305
+ ranges: match2.ranges,
32306
+ mode: "glob"
32307
+ } : null;
32308
+ }).filter((item) => item !== null).sort(compare);
32309
+ }
32310
+ const top = [];
32311
+ for (const item of items) {
32312
+ const match2 = matchPath(item.path);
32313
+ if (!match2)
32314
+ continue;
32315
+ const ranked = {
32316
+ item,
32317
+ score: match2.score,
32318
+ ranges: match2.ranges,
32319
+ mode: "glob"
32320
+ };
32321
+ pushBoundedTop(top, ranked, bounded, compare);
32322
+ }
32323
+ return top.sort(compare);
32324
+ }
32325
+ function rankPathMatches(query, items, limit) {
32326
+ if (isGlobPathQuery(query)) {
32327
+ return rankGlobPathMatches(query, items, limit);
32328
+ }
32329
+ return rankFuzzyPaths(query, items, limit).map((item) => ({
32330
+ ...item,
32331
+ mode: "fuzzy"
32332
+ }));
31393
32333
  }
31394
32334
  function isGlobPathQuery(query) {
31395
32335
  return /[*?]/.test(query.trim());
@@ -31434,48 +32374,39 @@ code-viewer query agent-help`
31434
32374
  }
31435
32375
  }
31436
32376
  function globMatchPath(query, path) {
32377
+ return createGlobPathMatcher(query)?.(path) ?? null;
32378
+ }
32379
+ function createGlobPathMatcher(query) {
31437
32380
  const regex = globToRegExp(query);
31438
- const baseStart = basenameStart(path);
31439
- const basename = path.slice(baseStart);
31440
- if (!regex || !regex.test(path) && (query.includes("/") || !regex.test(basename)))
32381
+ if (!regex)
31441
32382
  return null;
31442
32383
  const literal = query.replace(/[*?[\]]+/g, " ").trim().split(/\s+/).filter(Boolean);
31443
- const ranges = [];
31444
- const lowerPath = path.toLowerCase();
31445
- for (const part of literal) {
31446
- const start = lowerPath.indexOf(part.toLowerCase());
31447
- if (start >= 0)
31448
- ranges.push({ start, end: start + part.length });
31449
- }
31450
- ranges.sort((a2, b2) => a2.start - b2.start || a2.end - b2.end);
31451
- const mergedRanges = [];
31452
- for (const range of ranges) {
31453
- const last = mergedRanges[mergedRanges.length - 1];
31454
- if (last && last.end >= range.start) {
31455
- last.end = Math.max(last.end, range.end);
31456
- } else {
31457
- mergedRanges.push({ ...range });
32384
+ const suffix = query.replace(/^\*+/, "").toLowerCase();
32385
+ return (path) => {
32386
+ const baseStart = basenameStart(path);
32387
+ const basename = path.slice(baseStart);
32388
+ if (!regex.test(path) && (query.includes("/") || !regex.test(basename)))
32389
+ return null;
32390
+ const ranges = [];
32391
+ const lowerPath = path.toLowerCase();
32392
+ for (const part of literal) {
32393
+ const start = lowerPath.indexOf(part.toLowerCase());
32394
+ if (start >= 0)
32395
+ ranges.push({ start, end: start + part.length });
32396
+ }
32397
+ ranges.sort((a2, b2) => a2.start - b2.start || a2.end - b2.end);
32398
+ const mergedRanges = [];
32399
+ for (const range of ranges) {
32400
+ const last = mergedRanges[mergedRanges.length - 1];
32401
+ if (last && last.end >= range.start) {
32402
+ last.end = Math.max(last.end, range.end);
32403
+ } else {
32404
+ mergedRanges.push({ ...range });
32405
+ }
31458
32406
  }
31459
- }
31460
- const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(query.replace(/^\*+/, "").toLowerCase()) ? 50 : 0);
31461
- return { score, ranges: mergedRanges };
31462
- }
31463
- function rankPathMatches(query, items) {
31464
- if (isGlobPathQuery(query)) {
31465
- return items.map((item) => {
31466
- const match2 = globMatchPath(query, item.path);
31467
- return match2 ? {
31468
- item,
31469
- score: match2.score,
31470
- ranges: match2.ranges,
31471
- mode: "glob"
31472
- } : null;
31473
- }).filter((item) => item !== null).sort((a2, b2) => b2.score - a2.score || a2.item.path.localeCompare(b2.item.path));
31474
- }
31475
- return rankFuzzyPaths(query, items).map((item) => ({
31476
- ...item,
31477
- mode: "fuzzy"
31478
- }));
32407
+ const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(suffix) ? 50 : 0);
32408
+ return { score, ranges: mergedRanges };
32409
+ };
31479
32410
  }
31480
32411
 
31481
32412
  // web-src/core/search-palette.ts
@@ -31508,6 +32439,7 @@ code-viewer query agent-help`
31508
32439
  getServerGeneration
31509
32440
  } = deps;
31510
32441
  let PALETTE = null;
32442
+ let repoFileRequestGeneration = 0;
31511
32443
  const REPO_FILE_CACHE = new Map;
31512
32444
  function paletteSource() {
31513
32445
  if (STATE.route.screen === "diff")
@@ -31718,7 +32650,6 @@ code-viewer query agent-help`
31718
32650
  throw new Error("failed to load files");
31719
32651
  return r2.json();
31720
32652
  }));
31721
- REPO_FILE_CACHE.set(cacheKey, res);
31722
32653
  return res;
31723
32654
  }
31724
32655
  function diffFilePaletteItems(state, query) {
@@ -31770,10 +32701,19 @@ code-viewer query agent-help`
31770
32701
  } else {
31771
32702
  state.status.textContent = "Loading files...";
31772
32703
  const ref = paletteRef(source);
31773
- const response = await repoPaletteFiles(ref);
31774
- if (PALETTE !== state || state.input.value !== query)
32704
+ const requestGeneration = ++repoFileRequestGeneration;
32705
+ let response;
32706
+ try {
32707
+ response = await repoPaletteFiles(ref);
32708
+ } catch (err) {
32709
+ if (requestGeneration !== repoFileRequestGeneration)
32710
+ return;
32711
+ throw err;
32712
+ }
32713
+ if (PALETTE !== state || state.input.value !== query || requestGeneration !== repoFileRequestGeneration)
31775
32714
  return;
31776
- state.items = limitPaletteResults(rankPathMatches(query, response.files)).map((match2) => ({
32715
+ REPO_FILE_CACHE.set(repoFileCacheKey(ref), response);
32716
+ state.items = limitPaletteResults(rankPathMatches(query, response.files, PALETTE_RESULT_LIMIT)).map((match2) => ({
31777
32717
  kind: "file",
31778
32718
  path: match2.item.path,
31779
32719
  displayPath: match2.item.path,
@@ -31855,6 +32795,8 @@ code-viewer query agent-help`
31855
32795
  const query = state.input.value;
31856
32796
  if (state.mode === "file") {
31857
32797
  updateFilePalette(state, query).catch(() => {
32798
+ if (PALETTE !== state || state.input.value !== query)
32799
+ return;
31858
32800
  state.status.textContent = "Search failed";
31859
32801
  });
31860
32802
  } else {
@@ -31998,12 +32940,13 @@ code-viewer query agent-help`
31998
32940
  let sourceShikiLoadPromise = null;
31999
32941
  let PREFERRED_SOURCE_TAB = null;
32000
32942
  let SOURCE_CURSOR = null;
32943
+ let SOURCE_CURSOR_ROWS = [];
32001
32944
  const SOURCE_CURSOR_TOTALS = new Map;
32002
32945
  function sourceLineScrollAmount() {
32003
- const virtualRow = Array.from(document.querySelectorAll("#content .gdp-source-virtual-row")).find((item) => item.offsetParent !== null);
32946
+ const virtualRow = document.querySelector("#content .gdp-source-virtual:not([hidden]) .gdp-source-virtual-row");
32004
32947
  if (virtualRow)
32005
32948
  return virtualRow.getBoundingClientRect().height || VIRTUAL_SOURCE_ROW_HEIGHT;
32006
- const sourceRow = Array.from(document.querySelectorAll("#content .gdp-source-table tr")).find((item) => item.offsetParent !== null);
32949
+ const sourceRow = document.querySelector("#content .gdp-source-table:not([hidden]) tr");
32007
32950
  if (sourceRow)
32008
32951
  return sourceRow.getBoundingClientRect().height || 20;
32009
32952
  const preview = document.querySelector("#content .gdp-markdown-preview:not([hidden])");
@@ -32020,10 +32963,32 @@ code-viewer query agent-help`
32020
32963
  return !!SOURCE_CURSOR && sourceTargetsEqual(SOURCE_CURSOR.target, target) && SOURCE_CURSOR.line === line;
32021
32964
  }
32022
32965
  function syncSourceCursorRows(target) {
32023
- document.querySelectorAll("#content [data-line]").forEach((row) => {
32024
- const line = Number(row.dataset.line || "0");
32025
- row.classList.toggle("gdp-source-cursor", sourceCursorMatches(target, line));
32026
- });
32966
+ if (SOURCE_CURSOR_ROWS.length === 0 || SOURCE_CURSOR_ROWS.every((row) => !row.isConnected)) {
32967
+ SOURCE_CURSOR_ROWS = Array.from(document.querySelectorAll("#content .gdp-source-table tr.gdp-source-cursor, #content .gdp-source-virtual-row.gdp-source-cursor"));
32968
+ }
32969
+ for (const row of SOURCE_CURSOR_ROWS) {
32970
+ row.classList.remove("gdp-source-cursor");
32971
+ }
32972
+ SOURCE_CURSOR_ROWS = [];
32973
+ if (!SOURCE_CURSOR || !sourceTargetsEqual(SOURCE_CURSOR.target, target))
32974
+ return;
32975
+ const line = String(SOURCE_CURSOR.line);
32976
+ const table2 = document.querySelector("#content .gdp-source-table");
32977
+ const tableRow = table2?.rows[SOURCE_CURSOR.line - 1];
32978
+ if (tableRow?.dataset.line === line) {
32979
+ tableRow.classList.add("gdp-source-cursor");
32980
+ SOURCE_CURSOR_ROWS.push(tableRow);
32981
+ }
32982
+ const windowEl = document.querySelector("#content .gdp-source-virtual-window");
32983
+ if (windowEl) {
32984
+ const firstLine = Number(windowEl.firstElementChild?.dataset.line || "0");
32985
+ const offset = SOURCE_CURSOR.line - firstLine;
32986
+ const row = offset >= 0 ? windowEl.children[offset] : null;
32987
+ if (row?.dataset.line === line) {
32988
+ row.classList.add("gdp-source-cursor");
32989
+ SOURCE_CURSOR_ROWS.push(row);
32990
+ }
32991
+ }
32027
32992
  }
32028
32993
  function visibleSourceLineFallback() {
32029
32994
  const scroller = findMainScrollTarget();
@@ -35546,7 +36511,7 @@ code-viewer query agent-help`
35546
36511
  }
35547
36512
  let SERVER_GENERATION = 0;
35548
36513
  function trackLoad(promise) {
35549
- return NETWORK_ACTIVITY.track(promise);
36514
+ return promise;
35550
36515
  }
35551
36516
  function escapeHtml3(s2) {
35552
36517
  return String(s2 == null ? "" : s2).replace(/[&<>"']/g, (c2) => ({
@@ -36654,6 +37619,7 @@ code-viewer query agent-help`
36654
37619
  }, 200);
36655
37620
  });
36656
37621
  }
37622
+ let diffLoadGeneration = 0;
36657
37623
  function load(options = {}) {
36658
37624
  if (STATE.route.screen === "help") {
36659
37625
  setStatus("live");
@@ -36688,10 +37654,11 @@ code-viewer query agent-help`
36688
37654
  }
36689
37655
  }
36690
37656
  const routeAtRequest = STATE.route;
37657
+ const requestGeneration = ++diffLoadGeneration;
36691
37658
  const fromAtRequest = STATE.from;
36692
37659
  const toAtRequest = STATE.to;
36693
37660
  const ignoreWsAtRequest = STATE.ignoreWs;
36694
- const isCurrentDiffRequest = () => STATE.route === routeAtRequest && STATE.from === fromAtRequest && STATE.to === toAtRequest && STATE.ignoreWs === ignoreWsAtRequest;
37661
+ const isCurrentDiffRequest = () => requestGeneration === diffLoadGeneration && STATE.route === routeAtRequest && STATE.from === fromAtRequest && STATE.to === toAtRequest && STATE.ignoreWs === ignoreWsAtRequest;
36695
37662
  setStatus("refreshing");
36696
37663
  const params = new URLSearchParams;
36697
37664
  if (STATE.ignoreWs)