@walkthru-earth/objex 0.1.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/LICENSE +396 -0
- package/README.md +114 -0
- package/dist/assets/favicon.svg +17 -0
- package/dist/components/CLAUDE.md +44 -0
- package/dist/components/browser/Breadcrumb.svelte +50 -0
- package/dist/components/browser/Breadcrumb.svelte.d.ts +7 -0
- package/dist/components/browser/CreateFolderDialog.svelte +98 -0
- package/dist/components/browser/CreateFolderDialog.svelte.d.ts +6 -0
- package/dist/components/browser/DeleteConfirmDialog.svelte +90 -0
- package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +8 -0
- package/dist/components/browser/DropZone.svelte +83 -0
- package/dist/components/browser/DropZone.svelte.d.ts +7 -0
- package/dist/components/browser/FileBrowser.svelte +229 -0
- package/dist/components/browser/FileBrowser.svelte.d.ts +3 -0
- package/dist/components/browser/FileRow.svelte +112 -0
- package/dist/components/browser/FileRow.svelte.d.ts +9 -0
- package/dist/components/browser/FileTreeSidebar.svelte +559 -0
- package/dist/components/browser/FileTreeSidebar.svelte.d.ts +8 -0
- package/dist/components/browser/RenameDialog.svelte +101 -0
- package/dist/components/browser/RenameDialog.svelte.d.ts +8 -0
- package/dist/components/browser/SearchBar.svelte +40 -0
- package/dist/components/browser/SearchBar.svelte.d.ts +6 -0
- package/dist/components/browser/UploadButton.svelte +65 -0
- package/dist/components/browser/UploadButton.svelte.d.ts +3 -0
- package/dist/components/editor/CodeMirrorEditor.svelte +404 -0
- package/dist/components/editor/CodeMirrorEditor.svelte.d.ts +12 -0
- package/dist/components/editor/MilkdownEditor.svelte +98 -0
- package/dist/components/editor/MilkdownEditor.svelte.d.ts +9 -0
- package/dist/components/editor/SqlEditor.svelte +173 -0
- package/dist/components/editor/SqlEditor.svelte.d.ts +7 -0
- package/dist/components/editor/SqlResultBlock.svelte +199 -0
- package/dist/components/editor/SqlResultBlock.svelte.d.ts +9 -0
- package/dist/components/layout/ConnectionDialog.svelte +439 -0
- package/dist/components/layout/ConnectionDialog.svelte.d.ts +9 -0
- package/dist/components/layout/LocaleToggle.svelte +32 -0
- package/dist/components/layout/LocaleToggle.svelte.d.ts +3 -0
- package/dist/components/layout/SafeLockToggle.svelte +37 -0
- package/dist/components/layout/SafeLockToggle.svelte.d.ts +18 -0
- package/dist/components/layout/Sidebar.svelte +314 -0
- package/dist/components/layout/Sidebar.svelte.d.ts +3 -0
- package/dist/components/layout/StatusBar.svelte +73 -0
- package/dist/components/layout/StatusBar.svelte.d.ts +3 -0
- package/dist/components/layout/TabBar.svelte +102 -0
- package/dist/components/layout/TabBar.svelte.d.ts +7 -0
- package/dist/components/layout/ThemeToggle.svelte +52 -0
- package/dist/components/layout/ThemeToggle.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge.svelte +49 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +32 -0
- package/dist/components/ui/badge/index.d.ts +1 -0
- package/dist/components/ui/badge/index.js +1 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +64 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +40 -0
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte.d.ts +9 -0
- package/dist/components/ui/context-menu/context-menu-content.svelte +28 -0
- package/dist/components/ui/context-menu/context-menu-content.svelte.d.ts +10 -0
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte +21 -0
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte.d.ts +7 -0
- package/dist/components/ui/context-menu/context-menu-group.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-group.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-item.svelte +27 -0
- package/dist/components/ui/context-menu/context-menu-item.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-label.svelte +24 -0
- package/dist/components/ui/context-menu/context-menu-label.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-portal.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-portal.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte +16 -0
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte +33 -0
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-separator.svelte +17 -0
- package/dist/components/ui/context-menu/context-menu-separator.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte +20 -0
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte +20 -0
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +29 -0
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-sub.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-sub.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/context-menu-trigger.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/index.d.ts +17 -0
- package/dist/components/ui/context-menu/index.js +19 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +29 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +10 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +33 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/index.d.ts +18 -0
- package/dist/components/ui/dropdown-menu/index.js +18 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +52 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/resizable/index.d.ts +4 -0
- package/dist/components/ui/resizable/index.js +6 -0
- package/dist/components/ui/resizable/resizable-handle.svelte +30 -0
- package/dist/components/ui/resizable/resizable-handle.svelte.d.ts +8 -0
- package/dist/components/ui/resizable/resizable-pane-group.svelte +20 -0
- package/dist/components/ui/resizable/resizable-pane-group.svelte.d.ts +7 -0
- package/dist/components/ui/scroll-area/index.d.ts +3 -0
- package/dist/components/ui/scroll-area/index.js +5 -0
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +31 -0
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte.d.ts +4 -0
- package/dist/components/ui/scroll-area/scroll-area.svelte +47 -0
- package/dist/components/ui/scroll-area/scroll-area.svelte.d.ts +11 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/index.js +4 -0
- package/dist/components/ui/separator/separator.svelte +21 -0
- package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/index.d.ts +11 -0
- package/dist/components/ui/sheet/index.js +13 -0
- package/dist/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/components/ui/sheet/sheet-close.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-content.svelte +62 -0
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +37 -0
- package/dist/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte +20 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/components/ui/sheet/sheet-portal.svelte.d.ts +3 -0
- package/dist/components/ui/sheet/sheet-title.svelte +13 -0
- package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/components/ui/sheet/sheet-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet.svelte +7 -0
- package/dist/components/ui/sheet/sheet.svelte.d.ts +3 -0
- package/dist/components/ui/switch/index.d.ts +2 -0
- package/dist/components/ui/switch/index.js +4 -0
- package/dist/components/ui/switch/switch.svelte +28 -0
- package/dist/components/ui/switch/switch.svelte.d.ts +8 -0
- package/dist/components/ui/tabs/index.d.ts +5 -0
- package/dist/components/ui/tabs/index.js +7 -0
- package/dist/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +16 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +20 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs.svelte +19 -0
- package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/index.d.ts +6 -0
- package/dist/components/ui/tooltip/index.js +8 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
- package/dist/components/viewers/ArchiveViewer.svelte +586 -0
- package/dist/components/viewers/ArchiveViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CLAUDE.md +60 -0
- package/dist/components/viewers/CodeViewer.svelte +553 -0
- package/dist/components/viewers/CodeViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CogViewer.svelte +1345 -0
- package/dist/components/viewers/CogViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CopcViewer.svelte +25 -0
- package/dist/components/viewers/CopcViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/DatabaseViewer.svelte +169 -0
- package/dist/components/viewers/DatabaseViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/FileInfo.svelte +174 -0
- package/dist/components/viewers/FileInfo.svelte.d.ts +10 -0
- package/dist/components/viewers/FlatGeobufViewer.svelte +755 -0
- package/dist/components/viewers/FlatGeobufViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/GeoParquetMapViewer.svelte +278 -0
- package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +17 -0
- package/dist/components/viewers/ImageViewer.svelte +233 -0
- package/dist/components/viewers/ImageViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/LoadProgress.svelte +93 -0
- package/dist/components/viewers/LoadProgress.svelte.d.ts +15 -0
- package/dist/components/viewers/MapViewer.svelte +234 -0
- package/dist/components/viewers/MapViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/MarkdownViewer.svelte +478 -0
- package/dist/components/viewers/MarkdownViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/MediaViewer.svelte +121 -0
- package/dist/components/viewers/MediaViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/ModelViewer.svelte +164 -0
- package/dist/components/viewers/ModelViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/NotebookViewer.svelte +389 -0
- package/dist/components/viewers/NotebookViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/PdfViewer.svelte +278 -0
- package/dist/components/viewers/PdfViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/PmtilesViewer.svelte +191 -0
- package/dist/components/viewers/PmtilesViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/QueryHistoryPanel.svelte +159 -0
- package/dist/components/viewers/QueryHistoryPanel.svelte.d.ts +8 -0
- package/dist/components/viewers/RawViewer.svelte +117 -0
- package/dist/components/viewers/RawViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/StacMapViewer.svelte +20 -0
- package/dist/components/viewers/StacMapViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/StyleEditorOverlay.svelte +27 -0
- package/dist/components/viewers/StyleEditorOverlay.svelte.d.ts +7 -0
- package/dist/components/viewers/TableGrid.svelte +355 -0
- package/dist/components/viewers/TableGrid.svelte.d.ts +12 -0
- package/dist/components/viewers/TableStatusBar.svelte +92 -0
- package/dist/components/viewers/TableStatusBar.svelte.d.ts +11 -0
- package/dist/components/viewers/TableToolbar.svelte +382 -0
- package/dist/components/viewers/TableToolbar.svelte.d.ts +25 -0
- package/dist/components/viewers/TableViewer.svelte +923 -0
- package/dist/components/viewers/TableViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/ViewerRouter.svelte +70 -0
- package/dist/components/viewers/ViewerRouter.svelte.d.ts +7 -0
- package/dist/components/viewers/ZarrMapViewer.svelte +288 -0
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +17 -0
- package/dist/components/viewers/ZarrViewer.svelte +256 -0
- package/dist/components/viewers/ZarrViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/map/AttributeTable.svelte +52 -0
- package/dist/components/viewers/map/AttributeTable.svelte.d.ts +8 -0
- package/dist/components/viewers/map/MapContainer.svelte +158 -0
- package/dist/components/viewers/map/MapContainer.svelte.d.ts +12 -0
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +389 -0
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte.d.ts +10 -0
- package/dist/components/viewers/pmtiles/PmtilesMapView.svelte +332 -0
- package/dist/components/viewers/pmtiles/PmtilesMapView.svelte.d.ts +11 -0
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +373 -0
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte.d.ts +12 -0
- package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte +112 -0
- package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte.d.ts +10 -0
- package/dist/file-icons/CLAUDE.md +21 -0
- package/dist/file-icons/FileTypeIcon.svelte +74 -0
- package/dist/file-icons/FileTypeIcon.svelte.d.ts +9 -0
- package/dist/file-icons/index.d.ts +56 -0
- package/dist/file-icons/index.js +1070 -0
- package/dist/i18n/CLAUDE.md +19 -0
- package/dist/i18n/ar.d.ts +1 -0
- package/dist/i18n/ar.js +404 -0
- package/dist/i18n/en.d.ts +1 -0
- package/dist/i18n/en.js +404 -0
- package/dist/i18n/index.svelte.d.ts +9 -0
- package/dist/i18n/index.svelte.js +27 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +13 -0
- package/dist/query/CLAUDE.md +22 -0
- package/dist/query/engine.d.ts +56 -0
- package/dist/query/engine.js +6 -0
- package/dist/query/index.d.ts +4 -0
- package/dist/query/index.js +19 -0
- package/dist/query/wasm.d.ts +20 -0
- package/dist/query/wasm.js +890 -0
- package/dist/storage/CLAUDE.md +23 -0
- package/dist/storage/adapter.d.ts +21 -0
- package/dist/storage/adapter.js +1 -0
- package/dist/storage/browser-azure.d.ts +25 -0
- package/dist/storage/browser-azure.js +271 -0
- package/dist/storage/browser-cloud.d.ts +32 -0
- package/dist/storage/browser-cloud.js +293 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.js +37 -0
- package/dist/storage/url-adapter.d.ts +19 -0
- package/dist/storage/url-adapter.js +51 -0
- package/dist/stores/CLAUDE.md +29 -0
- package/dist/stores/browser.svelte.d.ts +28 -0
- package/dist/stores/browser.svelte.js +160 -0
- package/dist/stores/connections.svelte.d.ts +56 -0
- package/dist/stores/connections.svelte.js +272 -0
- package/dist/stores/credentials.svelte.d.ts +56 -0
- package/dist/stores/credentials.svelte.js +79 -0
- package/dist/stores/files.svelte.d.ts +20 -0
- package/dist/stores/files.svelte.js +76 -0
- package/dist/stores/query-history.svelte.d.ts +16 -0
- package/dist/stores/query-history.svelte.js +57 -0
- package/dist/stores/safelock.svelte.d.ts +8 -0
- package/dist/stores/safelock.svelte.js +52 -0
- package/dist/stores/settings.svelte.d.ts +11 -0
- package/dist/stores/settings.svelte.js +101 -0
- package/dist/stores/tab-resources.svelte.d.ts +25 -0
- package/dist/stores/tab-resources.svelte.js +61 -0
- package/dist/stores/tabs.svelte.d.ts +17 -0
- package/dist/stores/tabs.svelte.js +110 -0
- package/dist/types/notebookjs.d.ts +14 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/dist/utils/CLAUDE.md +54 -0
- package/dist/utils/analytics.d.ts +10 -0
- package/dist/utils/analytics.js +38 -0
- package/dist/utils/archive.d.ts +70 -0
- package/dist/utils/archive.js +333 -0
- package/dist/utils/column-types.d.ts +5 -0
- package/dist/utils/column-types.js +137 -0
- package/dist/utils/deck.d.ts +98 -0
- package/dist/utils/deck.js +208 -0
- package/dist/utils/evidence-context.d.ts +22 -0
- package/dist/utils/evidence-context.js +56 -0
- package/dist/utils/export.d.ts +2 -0
- package/dist/utils/export.js +51 -0
- package/dist/utils/format.d.ts +14 -0
- package/dist/utils/format.js +56 -0
- package/dist/utils/geoarrow.d.ts +32 -0
- package/dist/utils/geoarrow.js +672 -0
- package/dist/utils/hex.d.ts +10 -0
- package/dist/utils/hex.js +27 -0
- package/dist/utils/host-detection.d.ts +23 -0
- package/dist/utils/host-detection.js +289 -0
- package/dist/utils/map-selection.d.ts +12 -0
- package/dist/utils/map-selection.js +45 -0
- package/dist/utils/markdown-sql.d.ts +30 -0
- package/dist/utils/markdown-sql.js +73 -0
- package/dist/utils/markdown.d.ts +18 -0
- package/dist/utils/markdown.js +146 -0
- package/dist/utils/model3d.d.ts +13 -0
- package/dist/utils/model3d.js +62 -0
- package/dist/utils/parquet-metadata.d.ts +58 -0
- package/dist/utils/parquet-metadata.js +228 -0
- package/dist/utils/pdf.d.ts +8 -0
- package/dist/utils/pdf.js +28 -0
- package/dist/utils/pmtiles-tile.d.ts +38 -0
- package/dist/utils/pmtiles-tile.js +64 -0
- package/dist/utils/pmtiles.d.ts +46 -0
- package/dist/utils/pmtiles.js +135 -0
- package/dist/utils/shiki.d.ts +8 -0
- package/dist/utils/shiki.js +98 -0
- package/dist/utils/storage-url.d.ts +64 -0
- package/dist/utils/storage-url.js +374 -0
- package/dist/utils/url-state.d.ts +40 -0
- package/dist/utils/url-state.js +113 -0
- package/dist/utils/url.d.ts +27 -0
- package/dist/utils/url.js +115 -0
- package/dist/utils/wkb.d.ts +43 -0
- package/dist/utils/wkb.js +345 -0
- package/dist/utils/zarr.d.ts +39 -0
- package/dist/utils/zarr.js +204 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +5 -0
- package/package.json +203 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# storage/
|
|
2
|
+
|
|
3
|
+
Cloud storage adapters. All implement `StorageAdapter` interface.
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
graph LR
|
|
7
|
+
A[StorageAdapter] --> B[BrowserCloudAdapter<br/>S3/GCS/R2/MinIO/Storj]
|
|
8
|
+
A --> C[BrowserAzureAdapter<br/>Azure Blob + SAS]
|
|
9
|
+
A --> D[UrlAdapter<br/>direct HTTPS, read-only]
|
|
10
|
+
E[index.ts<br/>getAdapter / clearAdapterCache] --> B & C & D
|
|
11
|
+
B & C --> F[stores/connections]
|
|
12
|
+
B & C --> G[stores/credentials]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| File | Exports | Used by |
|
|
16
|
+
|------|---------|---------|
|
|
17
|
+
| `adapter.ts` | `StorageAdapter` (interface), `ListPage` | lib/index.ts (npm export) |
|
|
18
|
+
| `browser-cloud.ts` | `BrowserCloudAdapter` | index.ts (factory) |
|
|
19
|
+
| `browser-azure.ts` | `BrowserAzureAdapter` | index.ts (factory) |
|
|
20
|
+
| `url-adapter.ts` | `UrlAdapter` | lib/index.ts (npm export) |
|
|
21
|
+
| `index.ts` | `getAdapter()`, `clearAdapterCache()` | stores/browser, FileTreeSidebar, ArchiveViewer, ModelViewer, DatabaseViewer, MediaViewer, PdfViewer, RawViewer, MarkdownViewer, NotebookViewer, MapViewer, CodeViewer, ImageViewer |
|
|
22
|
+
|
|
23
|
+
`adapter.ts` and `url-adapter.ts` use relative imports (not `$lib`) — they're published to npm.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FileEntry, WriteResult } from '../types.js';
|
|
2
|
+
/** A single page of listing results with optional continuation. */
|
|
3
|
+
export interface ListPage {
|
|
4
|
+
entries: FileEntry[];
|
|
5
|
+
continuationToken?: string;
|
|
6
|
+
hasMore: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface StorageAdapter {
|
|
9
|
+
list(path: string, signal?: AbortSignal): Promise<FileEntry[]>;
|
|
10
|
+
read(path: string, offset?: number, length?: number, signal?: AbortSignal): Promise<Uint8Array>;
|
|
11
|
+
head(path: string, signal?: AbortSignal): Promise<FileEntry>;
|
|
12
|
+
/** Fetch a single page of listing results. Supports progressive rendering. */
|
|
13
|
+
listPage?(path: string, continuationToken?: string, pageSize?: number, signal?: AbortSignal): Promise<ListPage>;
|
|
14
|
+
put(key: string, data: Uint8Array, contentType?: string): Promise<WriteResult>;
|
|
15
|
+
delete(key: string): Promise<void>;
|
|
16
|
+
deletePrefix(prefix: string): Promise<{
|
|
17
|
+
deleted: number;
|
|
18
|
+
}>;
|
|
19
|
+
copy(srcKey: string, destKey: string): Promise<WriteResult>;
|
|
20
|
+
readonly supportsWrite: boolean;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FileEntry, WriteResult } from '../types.js';
|
|
2
|
+
import type { ListPage, StorageAdapter } from './adapter.js';
|
|
3
|
+
/**
|
|
4
|
+
* Browser-based Azure Blob Storage adapter.
|
|
5
|
+
*
|
|
6
|
+
* - Anonymous access uses plain `fetch()`.
|
|
7
|
+
* - Authenticated access appends SAS token as URL query params.
|
|
8
|
+
* - Requires CORS to be enabled on the storage account.
|
|
9
|
+
*/
|
|
10
|
+
export declare class BrowserAzureAdapter implements StorageAdapter {
|
|
11
|
+
private connectionId;
|
|
12
|
+
constructor(connectionId: string);
|
|
13
|
+
get supportsWrite(): boolean;
|
|
14
|
+
private getConnection;
|
|
15
|
+
listPage(path: string, continuationToken?: string, pageSize?: number, signal?: AbortSignal): Promise<ListPage>;
|
|
16
|
+
list(path: string): Promise<FileEntry[]>;
|
|
17
|
+
read(path: string, offset?: number, length?: number, signal?: AbortSignal): Promise<Uint8Array>;
|
|
18
|
+
head(path: string, signal?: AbortSignal): Promise<FileEntry>;
|
|
19
|
+
put(key: string, data: Uint8Array, contentType?: string): Promise<WriteResult>;
|
|
20
|
+
delete(key: string): Promise<void>;
|
|
21
|
+
deletePrefix(prefix: string): Promise<{
|
|
22
|
+
deleted: number;
|
|
23
|
+
}>;
|
|
24
|
+
copy(srcKey: string, destKey: string): Promise<WriteResult>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { connectionStore } from '../stores/connections.svelte.js';
|
|
2
|
+
import { credentialStore } from '../stores/credentials.svelte.js';
|
|
3
|
+
// --- Helpers ---
|
|
4
|
+
function nameFromKey(key) {
|
|
5
|
+
const trimmed = key.replace(/\/$/, '');
|
|
6
|
+
const segments = trimmed.split('/');
|
|
7
|
+
return segments[segments.length - 1] || trimmed;
|
|
8
|
+
}
|
|
9
|
+
function extensionFromName(name) {
|
|
10
|
+
const dot = name.lastIndexOf('.');
|
|
11
|
+
if (dot < 1)
|
|
12
|
+
return '';
|
|
13
|
+
return name.slice(dot + 1).toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the base URL for Azure Blob REST API.
|
|
17
|
+
* Format: https://<account>.blob.core.windows.net/<container>
|
|
18
|
+
*/
|
|
19
|
+
function buildBaseUrl(conn) {
|
|
20
|
+
const base = conn.endpoint.replace(/\/$/, '');
|
|
21
|
+
return `${base}/${conn.bucket}`;
|
|
22
|
+
}
|
|
23
|
+
function encodeKey(key) {
|
|
24
|
+
return key
|
|
25
|
+
.split('/')
|
|
26
|
+
.map((s) => encodeURIComponent(s))
|
|
27
|
+
.join('/');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the SAS token query string (with leading '?') if credentials exist.
|
|
31
|
+
*/
|
|
32
|
+
function getSasQuery(conn) {
|
|
33
|
+
if (conn.anonymous)
|
|
34
|
+
return '';
|
|
35
|
+
const creds = credentialStore.get(conn.id);
|
|
36
|
+
if (!creds || creds.type !== 'sas-token')
|
|
37
|
+
return '';
|
|
38
|
+
const token = creds.sasToken;
|
|
39
|
+
// SAS tokens may or may not start with '?'
|
|
40
|
+
return token.startsWith('?') ? token : `?${token}`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Append SAS token to a URL, handling existing query params.
|
|
44
|
+
*/
|
|
45
|
+
function appendSas(url, sasQuery) {
|
|
46
|
+
if (!sasQuery)
|
|
47
|
+
return url;
|
|
48
|
+
const sep = url.includes('?') ? '&' : '?';
|
|
49
|
+
// Strip leading '?' from sasQuery when appending with '&'
|
|
50
|
+
const token = sasQuery.startsWith('?') ? sasQuery.slice(1) : sasQuery;
|
|
51
|
+
return `${url}${sep}${token}`;
|
|
52
|
+
}
|
|
53
|
+
// --- Adapter ---
|
|
54
|
+
/**
|
|
55
|
+
* Browser-based Azure Blob Storage adapter.
|
|
56
|
+
*
|
|
57
|
+
* - Anonymous access uses plain `fetch()`.
|
|
58
|
+
* - Authenticated access appends SAS token as URL query params.
|
|
59
|
+
* - Requires CORS to be enabled on the storage account.
|
|
60
|
+
*/
|
|
61
|
+
export class BrowserAzureAdapter {
|
|
62
|
+
connectionId;
|
|
63
|
+
constructor(connectionId) {
|
|
64
|
+
this.connectionId = connectionId;
|
|
65
|
+
}
|
|
66
|
+
get supportsWrite() {
|
|
67
|
+
const conn = connectionStore.getById(this.connectionId);
|
|
68
|
+
return !!conn && !conn.anonymous;
|
|
69
|
+
}
|
|
70
|
+
getConnection() {
|
|
71
|
+
const conn = connectionStore.getById(this.connectionId);
|
|
72
|
+
if (!conn)
|
|
73
|
+
throw new Error(`Connection not found: ${this.connectionId}`);
|
|
74
|
+
return conn;
|
|
75
|
+
}
|
|
76
|
+
async listPage(path, continuationToken, pageSize, signal) {
|
|
77
|
+
const conn = this.getConnection();
|
|
78
|
+
const baseUrl = buildBaseUrl(conn);
|
|
79
|
+
const sas = getSasQuery(conn);
|
|
80
|
+
const params = new URLSearchParams({
|
|
81
|
+
restype: 'container',
|
|
82
|
+
comp: 'list',
|
|
83
|
+
delimiter: '/'
|
|
84
|
+
});
|
|
85
|
+
if (path)
|
|
86
|
+
params.set('prefix', path);
|
|
87
|
+
if (continuationToken)
|
|
88
|
+
params.set('marker', continuationToken);
|
|
89
|
+
if (pageSize)
|
|
90
|
+
params.set('maxresults', String(pageSize));
|
|
91
|
+
const url = appendSas(`${baseUrl}?${params}`, sas);
|
|
92
|
+
const res = await fetch(url, { signal });
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const body = await res.text().catch(() => '');
|
|
95
|
+
throw new Error(`Azure list failed (${res.status}): ${body || res.statusText}`);
|
|
96
|
+
}
|
|
97
|
+
const xml = await res.text();
|
|
98
|
+
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
99
|
+
const entries = [];
|
|
100
|
+
// Parse <BlobPrefix> (directories)
|
|
101
|
+
for (const bp of doc.querySelectorAll('BlobPrefix')) {
|
|
102
|
+
const prefix = bp.querySelector('Name')?.textContent ?? '';
|
|
103
|
+
if (!prefix)
|
|
104
|
+
continue;
|
|
105
|
+
const dirName = nameFromKey(prefix);
|
|
106
|
+
entries.push({
|
|
107
|
+
name: dirName,
|
|
108
|
+
path: prefix,
|
|
109
|
+
is_dir: true,
|
|
110
|
+
size: 0,
|
|
111
|
+
modified: 0,
|
|
112
|
+
extension: dirName.endsWith('.zarr') || dirName.endsWith('.zr3') ? 'zarr' : ''
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Parse <Blob> (files)
|
|
116
|
+
for (const blob of doc.querySelectorAll('Blobs > Blob')) {
|
|
117
|
+
const key = blob.querySelector('Name')?.textContent ?? '';
|
|
118
|
+
if (!key || key === path || key.endsWith('/'))
|
|
119
|
+
continue;
|
|
120
|
+
const name = nameFromKey(key);
|
|
121
|
+
const size = parseInt(blob.querySelector('Properties > Content-Length')?.textContent ?? '0', 10);
|
|
122
|
+
const lastMod = blob.querySelector('Properties > Last-Modified')?.textContent ?? '';
|
|
123
|
+
entries.push({
|
|
124
|
+
name,
|
|
125
|
+
path: key,
|
|
126
|
+
is_dir: false,
|
|
127
|
+
size,
|
|
128
|
+
modified: lastMod ? Date.parse(lastMod) || 0 : 0,
|
|
129
|
+
extension: extensionFromName(name)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const nextMarker = doc.querySelector('NextMarker')?.textContent || undefined;
|
|
133
|
+
return { entries, continuationToken: nextMarker, hasMore: !!nextMarker };
|
|
134
|
+
}
|
|
135
|
+
async list(path) {
|
|
136
|
+
const all = [];
|
|
137
|
+
let token;
|
|
138
|
+
do {
|
|
139
|
+
const page = await this.listPage(path, token);
|
|
140
|
+
all.push(...page.entries);
|
|
141
|
+
token = page.continuationToken;
|
|
142
|
+
} while (token);
|
|
143
|
+
return all;
|
|
144
|
+
}
|
|
145
|
+
async read(path, offset, length, signal) {
|
|
146
|
+
const conn = this.getConnection();
|
|
147
|
+
const url = appendSas(`${buildBaseUrl(conn)}/${encodeKey(path)}`, getSasQuery(conn));
|
|
148
|
+
const headers = {};
|
|
149
|
+
if (offset !== undefined || length !== undefined) {
|
|
150
|
+
const start = offset ?? 0;
|
|
151
|
+
const end = length !== undefined ? start + length - 1 : '';
|
|
152
|
+
headers.Range = `bytes=${start}-${end}`;
|
|
153
|
+
}
|
|
154
|
+
const res = await fetch(url, { headers, signal });
|
|
155
|
+
if (!res.ok && res.status !== 206) {
|
|
156
|
+
throw new Error(`Azure get failed (${res.status}): ${res.statusText}`);
|
|
157
|
+
}
|
|
158
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
159
|
+
}
|
|
160
|
+
async head(path, signal) {
|
|
161
|
+
const conn = this.getConnection();
|
|
162
|
+
const url = appendSas(`${buildBaseUrl(conn)}/${encodeKey(path)}`, getSasQuery(conn));
|
|
163
|
+
const res = await fetch(url, { method: 'HEAD', signal });
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
throw new Error(`Azure head failed (${res.status}): ${res.statusText}`);
|
|
166
|
+
}
|
|
167
|
+
const name = nameFromKey(path);
|
|
168
|
+
const size = parseInt(res.headers.get('content-length') ?? '0', 10);
|
|
169
|
+
const lastMod = res.headers.get('last-modified');
|
|
170
|
+
return {
|
|
171
|
+
name,
|
|
172
|
+
path,
|
|
173
|
+
is_dir: false,
|
|
174
|
+
size,
|
|
175
|
+
modified: lastMod ? Date.parse(lastMod) || 0 : 0,
|
|
176
|
+
extension: extensionFromName(name)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async put(key, data, contentType) {
|
|
180
|
+
const conn = this.getConnection();
|
|
181
|
+
const url = appendSas(`${buildBaseUrl(conn)}/${encodeKey(key)}`, getSasQuery(conn));
|
|
182
|
+
const headers = {
|
|
183
|
+
'x-ms-blob-type': 'BlockBlob'
|
|
184
|
+
};
|
|
185
|
+
if (contentType)
|
|
186
|
+
headers['Content-Type'] = contentType;
|
|
187
|
+
const res = await fetch(url, {
|
|
188
|
+
method: 'PUT',
|
|
189
|
+
body: data.buffer,
|
|
190
|
+
headers
|
|
191
|
+
});
|
|
192
|
+
if (!res.ok) {
|
|
193
|
+
const body = await res.text().catch(() => '');
|
|
194
|
+
throw new Error(`Azure put failed (${res.status}): ${body || res.statusText}`);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
key,
|
|
198
|
+
size: data.byteLength,
|
|
199
|
+
e_tag: res.headers.get('etag') ?? undefined
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async delete(key) {
|
|
203
|
+
const conn = this.getConnection();
|
|
204
|
+
const url = appendSas(`${buildBaseUrl(conn)}/${encodeKey(key)}`, getSasQuery(conn));
|
|
205
|
+
const res = await fetch(url, { method: 'DELETE' });
|
|
206
|
+
if (!res.ok && res.status !== 202 && res.status !== 204) {
|
|
207
|
+
throw new Error(`Azure delete failed (${res.status}): ${res.statusText}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async deletePrefix(prefix) {
|
|
211
|
+
if (!prefix) {
|
|
212
|
+
throw new Error('Cannot delete with empty prefix — this would delete the entire container.');
|
|
213
|
+
}
|
|
214
|
+
// List all blobs under prefix (no delimiter → recursive)
|
|
215
|
+
const conn = this.getConnection();
|
|
216
|
+
const baseUrl = buildBaseUrl(conn);
|
|
217
|
+
const sas = getSasQuery(conn);
|
|
218
|
+
const keys = [];
|
|
219
|
+
let marker;
|
|
220
|
+
do {
|
|
221
|
+
const params = new URLSearchParams({
|
|
222
|
+
restype: 'container',
|
|
223
|
+
comp: 'list'
|
|
224
|
+
});
|
|
225
|
+
if (prefix)
|
|
226
|
+
params.set('prefix', prefix);
|
|
227
|
+
if (marker)
|
|
228
|
+
params.set('marker', marker);
|
|
229
|
+
const url = appendSas(`${baseUrl}?${params}`, sas);
|
|
230
|
+
const res = await fetch(url);
|
|
231
|
+
if (!res.ok)
|
|
232
|
+
throw new Error(`Azure list failed (${res.status}): ${res.statusText}`);
|
|
233
|
+
const xml = await res.text();
|
|
234
|
+
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
235
|
+
for (const blob of doc.querySelectorAll('Blobs > Blob')) {
|
|
236
|
+
const key = blob.querySelector('Name')?.textContent;
|
|
237
|
+
if (key)
|
|
238
|
+
keys.push(key);
|
|
239
|
+
}
|
|
240
|
+
const nextMarker = doc.querySelector('NextMarker')?.textContent;
|
|
241
|
+
marker = nextMarker || undefined;
|
|
242
|
+
} while (marker);
|
|
243
|
+
for (const key of keys) {
|
|
244
|
+
await this.delete(key);
|
|
245
|
+
}
|
|
246
|
+
return { deleted: keys.length };
|
|
247
|
+
}
|
|
248
|
+
async copy(srcKey, destKey) {
|
|
249
|
+
const conn = this.getConnection();
|
|
250
|
+
const sas = getSasQuery(conn);
|
|
251
|
+
const base = buildBaseUrl(conn);
|
|
252
|
+
const destUrl = appendSas(`${base}/${encodeKey(destKey)}`, sas);
|
|
253
|
+
const srcUrl = appendSas(`${base}/${encodeKey(srcKey)}`, sas);
|
|
254
|
+
const res = await fetch(destUrl, {
|
|
255
|
+
method: 'PUT',
|
|
256
|
+
headers: {
|
|
257
|
+
'x-ms-copy-source': srcUrl,
|
|
258
|
+
'x-ms-blob-type': 'BlockBlob'
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
if (!res.ok) {
|
|
262
|
+
const body = await res.text().catch(() => '');
|
|
263
|
+
throw new Error(`Azure copy failed (${res.status}): ${body || res.statusText}`);
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
key: destKey,
|
|
267
|
+
size: 0,
|
|
268
|
+
e_tag: res.headers.get('etag') ?? undefined
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FileEntry, WriteResult } from '../types.js';
|
|
2
|
+
import type { ListPage, StorageAdapter } from './adapter.js';
|
|
3
|
+
/**
|
|
4
|
+
* Browser-based cloud storage adapter (S3-compatible).
|
|
5
|
+
*
|
|
6
|
+
* Supports S3, R2, GCS, Storj, MinIO, DigitalOcean Spaces, Wasabi, and other
|
|
7
|
+
* S3-compatible providers.
|
|
8
|
+
*
|
|
9
|
+
* - Anonymous connections use plain `fetch()`.
|
|
10
|
+
* - Authenticated connections use `aws4fetch` for SigV4 request signing.
|
|
11
|
+
* - Requires CORS to be enabled on the target bucket.
|
|
12
|
+
*/
|
|
13
|
+
export declare class BrowserCloudAdapter implements StorageAdapter {
|
|
14
|
+
private connectionId;
|
|
15
|
+
private _fetcher;
|
|
16
|
+
constructor(connectionId: string);
|
|
17
|
+
/** Invalidate cached fetcher (call if credentials change). */
|
|
18
|
+
resetFetcher(): void;
|
|
19
|
+
private getFetcher;
|
|
20
|
+
get supportsWrite(): boolean;
|
|
21
|
+
private getConnection;
|
|
22
|
+
listPage(path: string, continuationToken?: string, pageSize?: number, signal?: AbortSignal): Promise<ListPage>;
|
|
23
|
+
list(path: string): Promise<FileEntry[]>;
|
|
24
|
+
read(path: string, offset?: number, length?: number, signal?: AbortSignal): Promise<Uint8Array>;
|
|
25
|
+
head(path: string, signal?: AbortSignal): Promise<FileEntry>;
|
|
26
|
+
put(key: string, data: Uint8Array, contentType?: string): Promise<WriteResult>;
|
|
27
|
+
delete(key: string): Promise<void>;
|
|
28
|
+
deletePrefix(prefix: string): Promise<{
|
|
29
|
+
deleted: number;
|
|
30
|
+
}>;
|
|
31
|
+
copy(srcKey: string, destKey: string): Promise<WriteResult>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { AwsClient } from 'aws4fetch';
|
|
2
|
+
import { connectionStore } from '../stores/connections.svelte.js';
|
|
3
|
+
import { credentialStore } from '../stores/credentials.svelte.js';
|
|
4
|
+
// --- Helpers ---
|
|
5
|
+
/** Extract the last path segment from an object key. */
|
|
6
|
+
function nameFromKey(key) {
|
|
7
|
+
const trimmed = key.replace(/\/$/, '');
|
|
8
|
+
const segments = trimmed.split('/');
|
|
9
|
+
return segments[segments.length - 1] || trimmed;
|
|
10
|
+
}
|
|
11
|
+
/** Extract a file extension from a name, or return an empty string. */
|
|
12
|
+
function extensionFromName(name) {
|
|
13
|
+
const dot = name.lastIndexOf('.');
|
|
14
|
+
if (dot < 1)
|
|
15
|
+
return '';
|
|
16
|
+
return name.slice(dot + 1).toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the base URL for S3-compatible API requests.
|
|
20
|
+
* Uses path-style addressing (safer for buckets with dots in names).
|
|
21
|
+
*/
|
|
22
|
+
function buildBaseUrl(conn) {
|
|
23
|
+
if (conn.endpoint) {
|
|
24
|
+
const base = conn.endpoint.replace(/\/$/, '');
|
|
25
|
+
return `${base}/${conn.bucket}`;
|
|
26
|
+
}
|
|
27
|
+
// Default AWS S3 — path-style
|
|
28
|
+
return `https://s3.${conn.region}.amazonaws.com/${conn.bucket}`;
|
|
29
|
+
}
|
|
30
|
+
/** Decode a possibly percent-encoded S3 key (some providers URL-encode non-ASCII in XML). */
|
|
31
|
+
function decodeKey(key) {
|
|
32
|
+
try {
|
|
33
|
+
return decodeURIComponent(key);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return key;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Encode an object key for use in a URL path, preserving `/` separators. */
|
|
40
|
+
function encodeKey(key) {
|
|
41
|
+
return key
|
|
42
|
+
.split('/')
|
|
43
|
+
.map((s) => encodeURIComponent(s))
|
|
44
|
+
.join('/');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a fetch function that signs requests with SigV4 when credentials
|
|
48
|
+
* are available, or falls back to plain fetch for anonymous access.
|
|
49
|
+
*/
|
|
50
|
+
function createFetcher(conn) {
|
|
51
|
+
if (conn.anonymous) {
|
|
52
|
+
return (url, init) => fetch(url, init);
|
|
53
|
+
}
|
|
54
|
+
const creds = credentialStore.get(conn.id);
|
|
55
|
+
if (!creds || creds.type !== 'sigv4') {
|
|
56
|
+
throw new Error(`No credentials found for connection "${conn.name}". ` +
|
|
57
|
+
'Provide access key and secret key, or enable anonymous access.');
|
|
58
|
+
}
|
|
59
|
+
const client = new AwsClient({
|
|
60
|
+
accessKeyId: creds.accessKey,
|
|
61
|
+
secretAccessKey: creds.secretKey,
|
|
62
|
+
service: 's3',
|
|
63
|
+
region: conn.region
|
|
64
|
+
});
|
|
65
|
+
return (url, init) => client.fetch(url, init);
|
|
66
|
+
}
|
|
67
|
+
// --- Adapter ---
|
|
68
|
+
/**
|
|
69
|
+
* Browser-based cloud storage adapter (S3-compatible).
|
|
70
|
+
*
|
|
71
|
+
* Supports S3, R2, GCS, Storj, MinIO, DigitalOcean Spaces, Wasabi, and other
|
|
72
|
+
* S3-compatible providers.
|
|
73
|
+
*
|
|
74
|
+
* - Anonymous connections use plain `fetch()`.
|
|
75
|
+
* - Authenticated connections use `aws4fetch` for SigV4 request signing.
|
|
76
|
+
* - Requires CORS to be enabled on the target bucket.
|
|
77
|
+
*/
|
|
78
|
+
export class BrowserCloudAdapter {
|
|
79
|
+
connectionId;
|
|
80
|
+
_fetcher = null;
|
|
81
|
+
constructor(connectionId) {
|
|
82
|
+
this.connectionId = connectionId;
|
|
83
|
+
}
|
|
84
|
+
/** Invalidate cached fetcher (call if credentials change). */
|
|
85
|
+
resetFetcher() {
|
|
86
|
+
this._fetcher = null;
|
|
87
|
+
}
|
|
88
|
+
getFetcher() {
|
|
89
|
+
if (!this._fetcher) {
|
|
90
|
+
this._fetcher = createFetcher(this.getConnection());
|
|
91
|
+
}
|
|
92
|
+
return this._fetcher;
|
|
93
|
+
}
|
|
94
|
+
get supportsWrite() {
|
|
95
|
+
const conn = connectionStore.getById(this.connectionId);
|
|
96
|
+
return !!conn && !conn.anonymous;
|
|
97
|
+
}
|
|
98
|
+
getConnection() {
|
|
99
|
+
const conn = connectionStore.getById(this.connectionId);
|
|
100
|
+
if (!conn)
|
|
101
|
+
throw new Error(`Connection not found: ${this.connectionId}`);
|
|
102
|
+
return conn;
|
|
103
|
+
}
|
|
104
|
+
async listPage(path, continuationToken, pageSize, signal) {
|
|
105
|
+
const conn = this.getConnection();
|
|
106
|
+
const baseUrl = buildBaseUrl(conn);
|
|
107
|
+
const cloudFetch = this.getFetcher();
|
|
108
|
+
const params = new URLSearchParams({
|
|
109
|
+
'list-type': '2',
|
|
110
|
+
delimiter: '/'
|
|
111
|
+
});
|
|
112
|
+
if (path)
|
|
113
|
+
params.set('prefix', path);
|
|
114
|
+
if (continuationToken)
|
|
115
|
+
params.set('continuation-token', continuationToken);
|
|
116
|
+
if (pageSize)
|
|
117
|
+
params.set('max-keys', String(pageSize));
|
|
118
|
+
const res = await cloudFetch(`${baseUrl}?${params}`, { signal });
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const body = await res.text().catch(() => '');
|
|
121
|
+
throw new Error(`List failed (${res.status}): ${body || res.statusText}`);
|
|
122
|
+
}
|
|
123
|
+
const xml = await res.text();
|
|
124
|
+
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
125
|
+
const entries = [];
|
|
126
|
+
// Parse <CommonPrefixes> (directories)
|
|
127
|
+
for (const cp of doc.querySelectorAll('CommonPrefixes')) {
|
|
128
|
+
const prefix = cp.querySelector('Prefix')?.textContent ?? '';
|
|
129
|
+
if (!prefix)
|
|
130
|
+
continue;
|
|
131
|
+
const dirName = nameFromKey(prefix);
|
|
132
|
+
entries.push({
|
|
133
|
+
name: decodeKey(dirName),
|
|
134
|
+
path: prefix,
|
|
135
|
+
is_dir: true,
|
|
136
|
+
size: 0,
|
|
137
|
+
modified: 0,
|
|
138
|
+
extension: dirName.endsWith('.zarr') || dirName.endsWith('.zr3') ? 'zarr' : ''
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Parse <Contents> (files)
|
|
142
|
+
for (const item of doc.querySelectorAll('Contents')) {
|
|
143
|
+
const key = item.querySelector('Key')?.textContent ?? '';
|
|
144
|
+
if (!key || key === path || key.endsWith('/'))
|
|
145
|
+
continue;
|
|
146
|
+
const name = decodeKey(nameFromKey(key));
|
|
147
|
+
const size = parseInt(item.querySelector('Size')?.textContent ?? '0', 10);
|
|
148
|
+
const lastMod = item.querySelector('LastModified')?.textContent ?? '';
|
|
149
|
+
entries.push({
|
|
150
|
+
name,
|
|
151
|
+
path: key,
|
|
152
|
+
is_dir: false,
|
|
153
|
+
size,
|
|
154
|
+
modified: lastMod ? Date.parse(lastMod) || 0 : 0,
|
|
155
|
+
extension: extensionFromName(name)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const isTruncated = doc.querySelector('IsTruncated')?.textContent === 'true';
|
|
159
|
+
const nextToken = isTruncated
|
|
160
|
+
? (doc.querySelector('NextContinuationToken')?.textContent ?? undefined)
|
|
161
|
+
: undefined;
|
|
162
|
+
return { entries, continuationToken: nextToken, hasMore: !!nextToken };
|
|
163
|
+
}
|
|
164
|
+
async list(path) {
|
|
165
|
+
const all = [];
|
|
166
|
+
let token;
|
|
167
|
+
do {
|
|
168
|
+
const page = await this.listPage(path, token);
|
|
169
|
+
all.push(...page.entries);
|
|
170
|
+
token = page.continuationToken;
|
|
171
|
+
} while (token);
|
|
172
|
+
return all;
|
|
173
|
+
}
|
|
174
|
+
async read(path, offset, length, signal) {
|
|
175
|
+
const conn = this.getConnection();
|
|
176
|
+
const url = `${buildBaseUrl(conn)}/${encodeKey(path)}`;
|
|
177
|
+
const cloudFetch = this.getFetcher();
|
|
178
|
+
const headers = {};
|
|
179
|
+
if (offset !== undefined || length !== undefined) {
|
|
180
|
+
const start = offset ?? 0;
|
|
181
|
+
const end = length !== undefined ? start + length - 1 : '';
|
|
182
|
+
headers.Range = `bytes=${start}-${end}`;
|
|
183
|
+
}
|
|
184
|
+
const res = await cloudFetch(url, { headers, signal });
|
|
185
|
+
if (!res.ok && res.status !== 206) {
|
|
186
|
+
throw new Error(`Fetch failed (${res.status}): ${res.statusText}`);
|
|
187
|
+
}
|
|
188
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
189
|
+
}
|
|
190
|
+
async head(path, signal) {
|
|
191
|
+
const conn = this.getConnection();
|
|
192
|
+
const url = `${buildBaseUrl(conn)}/${encodeKey(path)}`;
|
|
193
|
+
const cloudFetch = this.getFetcher();
|
|
194
|
+
const res = await cloudFetch(url, { method: 'HEAD', signal });
|
|
195
|
+
if (!res.ok) {
|
|
196
|
+
throw new Error(`Head failed (${res.status}): ${res.statusText}`);
|
|
197
|
+
}
|
|
198
|
+
const name = nameFromKey(path);
|
|
199
|
+
const size = parseInt(res.headers.get('content-length') ?? '0', 10);
|
|
200
|
+
const lastMod = res.headers.get('last-modified');
|
|
201
|
+
return {
|
|
202
|
+
name,
|
|
203
|
+
path,
|
|
204
|
+
is_dir: false,
|
|
205
|
+
size,
|
|
206
|
+
modified: lastMod ? Date.parse(lastMod) || 0 : 0,
|
|
207
|
+
extension: extensionFromName(name)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async put(key, data, contentType) {
|
|
211
|
+
const conn = this.getConnection();
|
|
212
|
+
const url = `${buildBaseUrl(conn)}/${encodeKey(key)}`;
|
|
213
|
+
const cloudFetch = this.getFetcher();
|
|
214
|
+
const headers = {};
|
|
215
|
+
if (contentType)
|
|
216
|
+
headers['Content-Type'] = contentType;
|
|
217
|
+
const res = await cloudFetch(url, { method: 'PUT', body: data.buffer, headers });
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
const body = await res.text().catch(() => '');
|
|
220
|
+
throw new Error(`Upload failed (${res.status}): ${body || res.statusText}`);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
key,
|
|
224
|
+
size: data.byteLength,
|
|
225
|
+
e_tag: res.headers.get('etag') ?? undefined
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async delete(key) {
|
|
229
|
+
const conn = this.getConnection();
|
|
230
|
+
const url = `${buildBaseUrl(conn)}/${encodeKey(key)}`;
|
|
231
|
+
const cloudFetch = this.getFetcher();
|
|
232
|
+
const res = await cloudFetch(url, { method: 'DELETE' });
|
|
233
|
+
if (!res.ok && res.status !== 204) {
|
|
234
|
+
throw new Error(`Delete failed (${res.status}): ${res.statusText}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async deletePrefix(prefix) {
|
|
238
|
+
if (!prefix) {
|
|
239
|
+
throw new Error('Cannot delete with empty prefix — this would delete the entire bucket.');
|
|
240
|
+
}
|
|
241
|
+
// List all objects under prefix (no delimiter → recursive)
|
|
242
|
+
const conn = this.getConnection();
|
|
243
|
+
const baseUrl = buildBaseUrl(conn);
|
|
244
|
+
const cloudFetch = this.getFetcher();
|
|
245
|
+
const keys = [];
|
|
246
|
+
let continuationToken;
|
|
247
|
+
do {
|
|
248
|
+
const params = new URLSearchParams({ 'list-type': '2' });
|
|
249
|
+
if (prefix)
|
|
250
|
+
params.set('prefix', prefix);
|
|
251
|
+
if (continuationToken)
|
|
252
|
+
params.set('continuation-token', continuationToken);
|
|
253
|
+
const res = await cloudFetch(`${baseUrl}?${params}`);
|
|
254
|
+
if (!res.ok)
|
|
255
|
+
throw new Error(`List failed (${res.status}): ${res.statusText}`);
|
|
256
|
+
const xml = await res.text();
|
|
257
|
+
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
258
|
+
for (const item of doc.querySelectorAll('Contents')) {
|
|
259
|
+
const key = item.querySelector('Key')?.textContent;
|
|
260
|
+
if (key)
|
|
261
|
+
keys.push(key);
|
|
262
|
+
}
|
|
263
|
+
const isTruncated = doc.querySelector('IsTruncated')?.textContent === 'true';
|
|
264
|
+
continuationToken = isTruncated
|
|
265
|
+
? (doc.querySelector('NextContinuationToken')?.textContent ?? undefined)
|
|
266
|
+
: undefined;
|
|
267
|
+
} while (continuationToken);
|
|
268
|
+
for (const key of keys) {
|
|
269
|
+
await this.delete(key);
|
|
270
|
+
}
|
|
271
|
+
return { deleted: keys.length };
|
|
272
|
+
}
|
|
273
|
+
async copy(srcKey, destKey) {
|
|
274
|
+
const conn = this.getConnection();
|
|
275
|
+
const url = `${buildBaseUrl(conn)}/${encodeKey(destKey)}`;
|
|
276
|
+
const cloudFetch = this.getFetcher();
|
|
277
|
+
const res = await cloudFetch(url, {
|
|
278
|
+
method: 'PUT',
|
|
279
|
+
headers: {
|
|
280
|
+
'x-amz-copy-source': `/${conn.bucket}/${encodeKey(srcKey)}`
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
if (!res.ok) {
|
|
284
|
+
const body = await res.text().catch(() => '');
|
|
285
|
+
throw new Error(`Copy failed (${res.status}): ${body || res.statusText}`);
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
key: destKey,
|
|
289
|
+
size: 0,
|
|
290
|
+
e_tag: res.headers.get('etag') ?? undefined
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { StorageAdapter } from './adapter.js';
|
|
2
|
+
export type { StorageAdapter } from './adapter.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the appropriate storage adapter for the given source.
|
|
5
|
+
* Caches adapter instances per connectionId.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getAdapter(source: 'remote' | 'url', connectionId?: string): StorageAdapter;
|
|
8
|
+
/**
|
|
9
|
+
* Clear cached adapter for a connection (call on connection update/delete).
|
|
10
|
+
*/
|
|
11
|
+
export declare function clearAdapterCache(connectionId: string): void;
|