@youtyan/code-viewer 0.6.10 → 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
@@ -10352,6 +10352,24 @@ ${frontmatter.yaml}
10352
10352
  confirmDeleteObject: (key) => `Delete object "${key}"?`,
10353
10353
  editTextHint: "Only text objects can be edited in the browser.",
10354
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"
10355
10373
  }
10356
10374
  }
10357
10375
  };
@@ -10674,6 +10692,24 @@ ${frontmatter.yaml}
10674
10692
  confirmDeleteObject: (key) => `オブジェクト "${key}" を削除しますか?`,
10675
10693
  editTextHint: "ブラウザで編集できるのはテキストオブジェクトのみです。",
10676
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: "実行"
10677
10713
  }
10678
10714
  }
10679
10715
  };
@@ -10711,6 +10747,706 @@ ${frontmatter.yaml}
10711
10747
  el.appendChild(empty);
10712
10748
  }
10713
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
+
10714
11450
  // web-src/views/database/elasticsearch-explorer.ts
10715
11451
  function createElasticsearchExplorer(callbacks = {}) {
10716
11452
  const text2 = () => (callbacks.getText?.() ?? dbText("en")).explorer;
@@ -18769,7 +19505,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
18769
19505
  snapshotHidden: !sqlMode || tab !== "snapshot",
18770
19506
  redisHidden: kind !== "redis",
18771
19507
  esHidden: kind !== "elasticsearch",
18772
- s3Hidden: kind !== "s3"
19508
+ s3Hidden: kind !== "s3",
19509
+ dynamodbHidden: kind !== "dynamodb"
18773
19510
  };
18774
19511
  }
18775
19512
  function normalizeViewForDb(view, db) {
@@ -19151,7 +19888,14 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19151
19888
  });
19152
19889
  s3Explorer.el.hidden = true;
19153
19890
  s3Explorer.sidebarSlot.hidden = true;
19154
- 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);
19155
19899
  const noDatastoresPane = document.createElement("div");
19156
19900
  noDatastoresPane.className = "db-no-datastores";
19157
19901
  noDatastoresPane.hidden = true;
@@ -19175,7 +19919,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19175
19919
  }
19176
19920
  const mainContent = document.createElement("div");
19177
19921
  mainContent.className = "db-main-content";
19178
- 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);
19179
19923
  queryEditor.el.hidden = true;
19180
19924
  globalSearchView.el.hidden = true;
19181
19925
  snapshotView.el.hidden = true;
@@ -19303,10 +20047,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19303
20047
  redisExplorer.el.hidden = visibility.redisHidden;
19304
20048
  esExplorer.el.hidden = visibility.esHidden;
19305
20049
  s3Explorer.el.hidden = visibility.s3Hidden;
20050
+ dynamodbExplorer.el.hidden = visibility.dynamodbHidden;
19306
20051
  redisExplorer.sidebarSlot.hidden = visibility.redisHidden;
19307
20052
  esExplorer.sidebarSlot.hidden = visibility.esHidden;
19308
20053
  s3Explorer.sidebarSlot.hidden = visibility.s3Hidden;
19309
- 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;
19310
20056
  if (noDatastoresAvailable) {
19311
20057
  toolsSection.hidden = true;
19312
20058
  prefsBar.hidden = true;
@@ -19324,9 +20070,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19324
20070
  redisExplorer.el.hidden = true;
19325
20071
  esExplorer.el.hidden = true;
19326
20072
  s3Explorer.el.hidden = true;
20073
+ dynamodbExplorer.el.hidden = true;
19327
20074
  redisExplorer.sidebarSlot.hidden = true;
19328
20075
  esExplorer.sidebarSlot.hidden = true;
19329
20076
  s3Explorer.sidebarSlot.hidden = true;
20077
+ dynamodbExplorer.sidebarSlot.hidden = true;
19330
20078
  explorerSidebarHost.hidden = true;
19331
20079
  }
19332
20080
  if (!sqlMode) {
@@ -19679,7 +20427,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19679
20427
  return;
19680
20428
  tableSelectGuard.dispose();
19681
20429
  currentTable = null;
19682
- if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
20430
+ if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3" || currentDbInfo?.kind === "dynamodb") {
19683
20431
  currentSchema = null;
19684
20432
  renderSchemaOptions([], null);
19685
20433
  currentTab = "data";
@@ -19692,15 +20440,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19692
20440
  if (currentDbInfo.kind === "redis") {
19693
20441
  esExplorer.clear();
19694
20442
  s3Explorer.clear();
20443
+ dynamodbExplorer.clear();
19695
20444
  await redisExplorer.load(dbId, explorerInitial?.redis);
19696
20445
  } else if (currentDbInfo.kind === "elasticsearch") {
19697
20446
  redisExplorer.clear();
19698
20447
  s3Explorer.clear();
20448
+ dynamodbExplorer.clear();
19699
20449
  await esExplorer.load(dbId, explorerInitial?.es);
19700
- } else {
20450
+ } else if (currentDbInfo.kind === "s3") {
19701
20451
  redisExplorer.clear();
19702
20452
  esExplorer.clear();
20453
+ dynamodbExplorer.clear();
19703
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);
19704
20460
  }
19705
20461
  if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
19706
20462
  return;
@@ -19710,6 +20466,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
19710
20466
  redisExplorer.clear();
19711
20467
  esExplorer.clear();
19712
20468
  s3Explorer.clear();
20469
+ dynamodbExplorer.clear();
19713
20470
  applyVisibility();
19714
20471
  tableList.render([]);
19715
20472
  setTableListStatus(paneText().nav.loadingSchema);
@@ -20039,6 +20796,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20039
20796
  let pendingRedisInitial = initial.redis;
20040
20797
  let pendingEsInitial = initial.es;
20041
20798
  let pendingS3Initial = initial.s3;
20799
+ let pendingDynamodbInitial = initial.dynamodb;
20042
20800
  async function enter(db, schema, table2, view, options = {}) {
20043
20801
  const generation = ++loadGeneration;
20044
20802
  const filesResponse = await fetchDbFiles();
@@ -20071,6 +20829,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20071
20829
  redisExplorer.clear();
20072
20830
  esExplorer.clear();
20073
20831
  s3Explorer.clear();
20832
+ dynamodbExplorer.clear();
20074
20833
  renderNoDatastoresEmpty();
20075
20834
  applyVisibility();
20076
20835
  cb.onStateChange();
@@ -20106,6 +20865,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20106
20865
  redisExplorer.clear();
20107
20866
  esExplorer.clear();
20108
20867
  s3Explorer.clear();
20868
+ dynamodbExplorer.clear();
20109
20869
  setActiveTab("data", false);
20110
20870
  cb.onStateChange();
20111
20871
  return;
@@ -20116,15 +20876,17 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20116
20876
  const explorerInitial = {
20117
20877
  redis: pendingRedisInitial,
20118
20878
  es: pendingEsInitial,
20119
- s3: pendingS3Initial
20879
+ s3: pendingS3Initial,
20880
+ dynamodb: pendingDynamodbInitial
20120
20881
  };
20121
20882
  pendingRedisInitial = undefined;
20122
20883
  pendingEsInitial = undefined;
20123
20884
  pendingS3Initial = undefined;
20885
+ pendingDynamodbInitial = undefined;
20124
20886
  await selectDb(target, explorerInitial, generation, schema !== undefined ? schema : currentSchema, table2, view);
20125
20887
  if (generation !== loadGeneration || currentDbInfo?.id !== target)
20126
20888
  return;
20127
- if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
20889
+ if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3" || currentDbInfo?.kind === "dynamodb") {
20128
20890
  cb.onStateChange();
20129
20891
  return;
20130
20892
  }
@@ -20229,6 +20991,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20229
20991
  state.es = initial.es;
20230
20992
  if (initial.s3)
20231
20993
  state.s3 = initial.s3;
20994
+ if (initial.dynamodb)
20995
+ state.dynamodb = initial.dynamodb;
20232
20996
  } else if (currentDbInfo?.kind === "redis") {
20233
20997
  const sel = redisExplorer.getSelection();
20234
20998
  if (sel.dbIndex !== undefined || sel.key !== undefined || sel.keyFilter !== undefined) {
@@ -20244,6 +21008,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20244
21008
  if (sel.bucket !== undefined || sel.prefix !== undefined || sel.query !== undefined || sel.key !== undefined || sel.mode !== "prefix" || sel.sort !== "updated-desc" || sel.view === "explorer") {
20245
21009
  state.s3 = sel;
20246
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
+ }
20247
21016
  }
20248
21017
  return state;
20249
21018
  }
@@ -20300,6 +21069,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20300
21069
  redisExplorer.dispose();
20301
21070
  esExplorer.dispose();
20302
21071
  s3Explorer.dispose();
21072
+ dynamodbExplorer.dispose();
20303
21073
  currentDbInfo = null;
20304
21074
  currentSchema = null;
20305
21075
  currentTable = null;
@@ -20320,6 +21090,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20320
21090
  redisExplorer.localize();
20321
21091
  esExplorer.localize();
20322
21092
  s3Explorer.localize();
21093
+ dynamodbExplorer.localize();
20323
21094
  }
20324
21095
  return {
20325
21096
  el: container,
@@ -20732,7 +21503,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20732
21503
  view: tab.view,
20733
21504
  redis: tab.redis ?? null,
20734
21505
  es: tab.es ?? null,
20735
- s3: tab.s3 ?? null
21506
+ s3: tab.s3 ?? null,
21507
+ dynamodb: tab.dynamodb ?? null
20736
21508
  });
20737
21509
  if (seen.has(key))
20738
21510
  continue;
@@ -20990,7 +21762,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20990
21762
  detailPanelHeight: initial?.detailPanelHeight,
20991
21763
  redis: initial?.redis,
20992
21764
  es: initial?.es,
20993
- s3: initial?.s3
21765
+ s3: initial?.s3,
21766
+ dynamodb: initial?.dynamodb
20994
21767
  });
20995
21768
  pane.el.hidden = true;
20996
21769
  tabsList.appendChild(chip);
@@ -24761,7 +25534,7 @@ code-viewer annotate add-db --db app.db --tab query \\
24761
25534
  database: {
24762
25535
  nav: "Datastores",
24763
25536
  title: "Datastore Viewer",
24764
- 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.",
24765
25538
  groups: [
24766
25539
  {
24767
25540
  title: "Supported datastores",
@@ -24789,6 +25562,10 @@ code-viewer annotate add-db --db app.db --tab query \\
24789
25562
  "Elasticsearch",
24790
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."
24791
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
+ ],
24792
25569
  [
24793
25570
  "S3 / MinIO / LocalStack",
24794
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."
@@ -24845,7 +25622,7 @@ code-viewer annotate add-db --db app.db --tab query \\
24845
25622
  ],
24846
25623
  [
24847
25624
  "Datastore explorers",
24848
- "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."
24849
25626
  ]
24850
25627
  ]
24851
25628
  }
@@ -25420,7 +26197,7 @@ code-viewer annotate add-db --db app.db --tab query \\
25420
26197
  database: {
25421
26198
  nav: "データストア",
25422
26199
  title: "データストアビューア",
25423
- intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、S3 互換オブジェクトストアをローカルビューアで閲覧・編集できます。",
26200
+ intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、DynamoDB、S3 互換オブジェクトストアをローカルビューアで閲覧できます。",
25424
26201
  groups: [
25425
26202
  {
25426
26203
  title: "対応データストア",
@@ -25448,6 +26225,10 @@ code-viewer annotate add-db --db app.db --tab query \\
25448
26225
  "Elasticsearch",
25449
26226
  "compose ファイルから検出。インデックス一覧、マッピング、search_after ページング、lucene q= と許可リスト経由の DSL、スナップショット/差分に対応。_seq_no / _primary_term 楽観ロックでドキュメントの編集 / 新規作成 / 削除も可能。"
25450
26227
  ],
26228
+ [
26229
+ "DynamoDB / LocalStack",
26230
+ "LocalStack の compose サービスで DynamoDB が有効な場合に検出。テーブル一覧、キースキーマ、scan / query、継続トークンによるページング、コピー可能なキー付きのアイテム詳細を表示します。閲覧専用です。"
26231
+ ],
25451
26232
  [
25452
26233
  "S3 / MinIO / LocalStack",
25453
26234
  "compose ファイルから検出。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
@@ -25504,7 +26285,7 @@ code-viewer annotate add-db --db app.db --tab query \\
25504
26285
  ],
25505
26286
  [
25506
26287
  "データストア専用エクスプローラ",
25507
- "Redis / Elasticsearch / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー / フォルダツリーに差し替えます。それぞれインライン編集ペインと確認ダイアログによる値編集 / ドキュメント・キー・オブジェクト作成 / 削除に対応。"
26288
+ "Redis / Elasticsearch / DynamoDB / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー / テーブル一覧 / フォルダツリーに差し替えます。Redis、Elasticsearch、S3 は編集・作成フローを提供し、DynamoDB は閲覧専用です。"
25508
26289
  ]
25509
26290
  ]
25510
26291
  }
@@ -31658,6 +32439,7 @@ code-viewer query agent-help`
31658
32439
  getServerGeneration
31659
32440
  } = deps;
31660
32441
  let PALETTE = null;
32442
+ let repoFileRequestGeneration = 0;
31661
32443
  const REPO_FILE_CACHE = new Map;
31662
32444
  function paletteSource() {
31663
32445
  if (STATE.route.screen === "diff")
@@ -31868,7 +32650,6 @@ code-viewer query agent-help`
31868
32650
  throw new Error("failed to load files");
31869
32651
  return r2.json();
31870
32652
  }));
31871
- REPO_FILE_CACHE.set(cacheKey, res);
31872
32653
  return res;
31873
32654
  }
31874
32655
  function diffFilePaletteItems(state, query) {
@@ -31920,9 +32701,18 @@ code-viewer query agent-help`
31920
32701
  } else {
31921
32702
  state.status.textContent = "Loading files...";
31922
32703
  const ref = paletteRef(source);
31923
- const response = await repoPaletteFiles(ref);
31924
- 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)
31925
32714
  return;
32715
+ REPO_FILE_CACHE.set(repoFileCacheKey(ref), response);
31926
32716
  state.items = limitPaletteResults(rankPathMatches(query, response.files, PALETTE_RESULT_LIMIT)).map((match2) => ({
31927
32717
  kind: "file",
31928
32718
  path: match2.item.path,
@@ -36829,6 +37619,7 @@ code-viewer query agent-help`
36829
37619
  }, 200);
36830
37620
  });
36831
37621
  }
37622
+ let diffLoadGeneration = 0;
36832
37623
  function load(options = {}) {
36833
37624
  if (STATE.route.screen === "help") {
36834
37625
  setStatus("live");
@@ -36863,10 +37654,11 @@ code-viewer query agent-help`
36863
37654
  }
36864
37655
  }
36865
37656
  const routeAtRequest = STATE.route;
37657
+ const requestGeneration = ++diffLoadGeneration;
36866
37658
  const fromAtRequest = STATE.from;
36867
37659
  const toAtRequest = STATE.to;
36868
37660
  const ignoreWsAtRequest = STATE.ignoreWs;
36869
- 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;
36870
37662
  setStatus("refreshing");
36871
37663
  const params = new URLSearchParams;
36872
37664
  if (STATE.ignoreWs)