@walkthru-earth/objex-utils 1.3.1 → 1.5.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/dist/index.js CHANGED
@@ -14,6 +14,9 @@ var SQL_PREVIEW_LENGTH = 120;
14
14
  var VIEWER_DIR_EXTENSIONS = /* @__PURE__ */ new Set(["zarr", "zr3"]);
15
15
  var LAYER_HUE_MULTIPLIER = 137;
16
16
  var COPY_FEEDBACK_MS = 2e3;
17
+ var DEFAULT_AWS_REGION = "us-east-1";
18
+ var TILE_DEBOUNCE_MS = 200;
19
+ var FIRST_FEATURE_FLY_ZOOM = 14;
17
20
 
18
21
  // ../../src/lib/file-icons/index.ts
19
22
  var DEFAULT_INFO = {
@@ -1148,15 +1151,15 @@ var PROVIDERS = {
1148
1151
  schemes: ["azure", "az", "abfs", "abfss", "wasbs", "adl"]
1149
1152
  },
1150
1153
  minio: {
1151
- label: "MinIO",
1152
- description: "Self-hosted MinIO or S3-compatible",
1154
+ label: "MinIO / RustFS / Custom",
1155
+ description: "MinIO, RustFS, or any custom S3-compatible endpoint",
1153
1156
  authMethod: "sigv4",
1154
1157
  needsRegion: false,
1155
1158
  needsEndpoint: true,
1156
1159
  defaultRegion: "us-east-1",
1157
1160
  endpointTemplate: null,
1158
1161
  regions: [],
1159
- endpointPlaceholder: "https://minio.example.com or http://localhost:9000",
1162
+ endpointPlaceholder: "https://s3.example.com or http://localhost:9000",
1160
1163
  schemes: []
1161
1164
  },
1162
1165
  storj: {
@@ -1364,7 +1367,7 @@ function buildProviderBaseUrl(provider, endpoint, bucket, region) {
1364
1367
  const resolved = def.endpointTemplate.replace("{region}", region || def.defaultRegion);
1365
1368
  return `${resolved}/${bucket}`;
1366
1369
  }
1367
- return `https://s3.${region || "us-east-1"}.amazonaws.com/${bucket}`;
1370
+ return `https://s3.${region || DEFAULT_AWS_REGION}.amazonaws.com/${bucket}`;
1368
1371
  }
1369
1372
  function isGcsProvider(provider, endpoint) {
1370
1373
  return provider === "gcs" || !!endpoint && /storage\.googleapis\.com/i.test(endpoint);
@@ -1424,7 +1427,475 @@ var UrlAdapter = class {
1424
1427
  }
1425
1428
  };
1426
1429
 
1427
- // ../../src/lib/utils/cloud-url.ts
1430
+ // src/app-config.ts
1431
+ var DEFAULT_APP_CONFIG = {
1432
+ defaults: { theme: "system", locale: "en", featureLimit: 1e3, mosaicItemLimit: 2e3 },
1433
+ ui: { showConnectionRail: true, showFileTree: true, showSettings: true },
1434
+ basemaps: [
1435
+ {
1436
+ id: "positron",
1437
+ label: "Positron",
1438
+ type: "vector",
1439
+ url: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
1440
+ variant: "light"
1441
+ },
1442
+ {
1443
+ id: "dark-matter",
1444
+ label: "Dark Matter",
1445
+ type: "vector",
1446
+ url: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
1447
+ variant: "dark"
1448
+ }
1449
+ ],
1450
+ defaultBasemap: { light: "positron", dark: "dark-matter" },
1451
+ connections: []
1452
+ };
1453
+ function asObject(v) {
1454
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
1455
+ }
1456
+ function coerceTheme(v) {
1457
+ return v === "light" || v === "dark" || v === "system" ? v : void 0;
1458
+ }
1459
+ function coerceString(v) {
1460
+ return typeof v === "string" && v.trim().length > 0 ? v.trim() : void 0;
1461
+ }
1462
+ function coercePositiveInt(v) {
1463
+ return typeof v === "number" && Number.isFinite(v) && v >= 1 ? Math.floor(v) : void 0;
1464
+ }
1465
+ function coerceBool(v) {
1466
+ return typeof v === "boolean" ? v : void 0;
1467
+ }
1468
+ function resolveSetting(...candidates) {
1469
+ for (const c of candidates) {
1470
+ if (c !== null && c !== void 0) return c;
1471
+ }
1472
+ return void 0;
1473
+ }
1474
+ function parseVisibilityParam(value) {
1475
+ if (value === "hide") return false;
1476
+ if (value === "show") return true;
1477
+ return void 0;
1478
+ }
1479
+ function coerceBasemaps(v) {
1480
+ if (!Array.isArray(v)) return void 0;
1481
+ const out = [];
1482
+ for (const raw of v) {
1483
+ const o = asObject(raw);
1484
+ if (!o) continue;
1485
+ const id = coerceString(o.id);
1486
+ const label = coerceString(o.label);
1487
+ const url = coerceString(o.url);
1488
+ const type = o.type === "vector" || o.type === "raster" ? o.type : void 0;
1489
+ if (!id || !label || !url || !type) continue;
1490
+ const variant = o.variant === "light" || o.variant === "dark" ? o.variant : void 0;
1491
+ out.push({ id, label, url, type, ...variant ? { variant } : {} });
1492
+ }
1493
+ return out;
1494
+ }
1495
+ function coerceConnections(v) {
1496
+ if (!Array.isArray(v)) return void 0;
1497
+ const out = [];
1498
+ for (const raw of v) {
1499
+ const o = asObject(raw);
1500
+ if (!o) continue;
1501
+ const name = coerceString(o.name);
1502
+ const bucket = coerceString(o.bucket);
1503
+ if (!name || !bucket) continue;
1504
+ const region = coerceString(o.region);
1505
+ const endpoint = coerceString(o.endpoint);
1506
+ const anonymous = coerceBool(o.anonymous);
1507
+ const authMethod = o.authMethod === "sigv4" || o.authMethod === "sas-token" ? o.authMethod : void 0;
1508
+ const rootPrefix = coerceString(o.rootPrefix);
1509
+ out.push({
1510
+ name,
1511
+ bucket,
1512
+ provider: coerceString(o.provider) ?? "s3",
1513
+ ...region !== void 0 ? { region } : {},
1514
+ ...endpoint !== void 0 ? { endpoint } : {},
1515
+ ...anonymous !== void 0 ? { anonymous } : {},
1516
+ ...authMethod !== void 0 ? { authMethod } : {},
1517
+ ...rootPrefix !== void 0 ? { rootPrefix } : {}
1518
+ });
1519
+ }
1520
+ return out;
1521
+ }
1522
+ function resolveBasemap(config, variant, userId) {
1523
+ const list = config.basemaps;
1524
+ if (list.length === 0) return void 0;
1525
+ if (userId) {
1526
+ const picked = list.find((b) => b.id === userId);
1527
+ if (picked) return picked;
1528
+ }
1529
+ const defaultId = config.defaultBasemap[variant];
1530
+ if (defaultId) {
1531
+ const byDefault = list.find((b) => b.id === defaultId);
1532
+ if (byDefault) return byDefault;
1533
+ }
1534
+ return list.find((b) => b.variant === variant) ?? list[0];
1535
+ }
1536
+ function mergeAppConfig(base, override) {
1537
+ const o = asObject(override);
1538
+ if (!o) return base;
1539
+ const d = asObject(o.defaults) ?? {};
1540
+ const u = asObject(o.ui) ?? {};
1541
+ const db = asObject(o.defaultBasemap) ?? {};
1542
+ return {
1543
+ defaults: {
1544
+ theme: coerceTheme(d.theme) ?? base.defaults.theme,
1545
+ locale: coerceString(d.locale) ?? base.defaults.locale,
1546
+ featureLimit: coercePositiveInt(d.featureLimit) ?? base.defaults.featureLimit,
1547
+ mosaicItemLimit: coercePositiveInt(d.mosaicItemLimit) ?? base.defaults.mosaicItemLimit
1548
+ },
1549
+ ui: {
1550
+ showConnectionRail: coerceBool(u.showConnectionRail) ?? base.ui.showConnectionRail,
1551
+ showFileTree: coerceBool(u.showFileTree) ?? base.ui.showFileTree,
1552
+ showSettings: coerceBool(u.showSettings) ?? base.ui.showSettings
1553
+ },
1554
+ basemaps: coerceBasemaps(o.basemaps) ?? base.basemaps,
1555
+ defaultBasemap: (() => {
1556
+ const light = coerceString(db.light) ?? base.defaultBasemap.light;
1557
+ const dark = coerceString(db.dark) ?? base.defaultBasemap.dark;
1558
+ return { ...light !== void 0 ? { light } : {}, ...dark !== void 0 ? { dark } : {} };
1559
+ })(),
1560
+ connections: coerceConnections(o.connections) ?? base.connections
1561
+ };
1562
+ }
1563
+
1564
+ // src/stac.ts
1565
+ var STAC_COG_ASSET_KEYS = ["visual", "image", "data", "rendered_preview"];
1566
+ function isStacItem(json) {
1567
+ if (!json || typeof json !== "object") return false;
1568
+ const obj = json;
1569
+ return obj.type === "Feature" && typeof obj.stac_version === "string";
1570
+ }
1571
+ function isStacFeatureCollection(json) {
1572
+ if (!json || typeof json !== "object") return false;
1573
+ const obj = json;
1574
+ if (obj.type !== "FeatureCollection") return false;
1575
+ if (!Array.isArray(obj.features) || obj.features.length === 0) return false;
1576
+ if (typeof obj.stac_version === "string") return true;
1577
+ return isStacItem(obj.features[0]);
1578
+ }
1579
+ function isStacCollection(json) {
1580
+ if (!json || typeof json !== "object") return false;
1581
+ const obj = json;
1582
+ return obj.type === "Collection" && typeof obj.stac_version === "string" && Array.isArray(obj.links);
1583
+ }
1584
+ function isStacCatalog(json) {
1585
+ if (!json || typeof json !== "object") return false;
1586
+ const obj = json;
1587
+ return obj.type === "Catalog" && typeof obj.stac_version === "string" && Array.isArray(obj.links);
1588
+ }
1589
+ function classifyStac(json) {
1590
+ if (isStacItem(json)) return { kind: "item", item: json };
1591
+ if (isStacFeatureCollection(json)) return { kind: "item-collection", fc: json };
1592
+ if (isStacCollection(json)) return { kind: "collection", payload: json };
1593
+ if (isStacCatalog(json)) return { kind: "catalog", payload: json };
1594
+ return { kind: "none" };
1595
+ }
1596
+ function pickCogAssetHref(item, preferred) {
1597
+ const assets = item.assets ?? {};
1598
+ if (preferred && assets[preferred]?.href) return assets[preferred].href;
1599
+ for (const key of STAC_COG_ASSET_KEYS) {
1600
+ if (assets[key]?.href) return assets[key].href;
1601
+ }
1602
+ for (const asset of Object.values(assets)) {
1603
+ const t = typeof asset?.type === "string" ? asset.type.toLowerCase() : "";
1604
+ if (asset?.href && t.includes("tiff")) return asset.href;
1605
+ }
1606
+ return null;
1607
+ }
1608
+ function detectMosaicCapable(item) {
1609
+ return stacItemBbox(item) !== null && pickCogAssetHref(item) !== null;
1610
+ }
1611
+ function detectMultiCogCapable(item) {
1612
+ if (hasRgbBands(extractSentinelBandAssets(item))) return true;
1613
+ return hasCompositableBands(extractRasterBandAssets(item));
1614
+ }
1615
+ function stacItemBbox(item) {
1616
+ if (Array.isArray(item.bbox) && item.bbox.length >= 4) {
1617
+ return [Number(item.bbox[0]), Number(item.bbox[1]), Number(item.bbox[2]), Number(item.bbox[3])];
1618
+ }
1619
+ return null;
1620
+ }
1621
+ function buildMosaicSourceMeta(input, assetKey) {
1622
+ if (!input || typeof input !== "object") return null;
1623
+ if (isStacItem(input)) {
1624
+ const bbox = stacItemBbox(input);
1625
+ if (!bbox) return null;
1626
+ const href = pickCogAssetHref(input, assetKey);
1627
+ if (!href) return null;
1628
+ return {
1629
+ id: String(input.id ?? href),
1630
+ bbox,
1631
+ href
1632
+ };
1633
+ }
1634
+ const raw = input;
1635
+ if (Array.isArray(raw.bbox) && raw.bbox.length >= 4 && typeof raw.href === "string") {
1636
+ return {
1637
+ id: String(raw.id ?? raw.href),
1638
+ bbox: [Number(raw.bbox[0]), Number(raw.bbox[1]), Number(raw.bbox[2]), Number(raw.bbox[3])],
1639
+ href: raw.href
1640
+ };
1641
+ }
1642
+ return null;
1643
+ }
1644
+ function spatialCellKey(item, bbox) {
1645
+ const props = item.properties ?? {};
1646
+ const grid = props["grid:code"];
1647
+ if (typeof grid === "string" && grid) return `g:${grid}`;
1648
+ const utm = props["mgrs:utm_zone"];
1649
+ const band = props["mgrs:latitude_band"];
1650
+ const sq = props["mgrs:grid_square"];
1651
+ if (utm != null && band != null && sq != null) return `m:${utm}${band}${sq}`;
1652
+ const s2 = props["s2:mgrs_tile"];
1653
+ if (typeof s2 === "string" && s2) return `m:${s2}`;
1654
+ return `b:${bbox[0].toFixed(3)},${bbox[1].toFixed(3)},${bbox[2].toFixed(3)},${bbox[3].toFixed(3)}`;
1655
+ }
1656
+ var BAND_KEY_FALLBACKS = {
1657
+ red: ["red", "B04", "B4", "visual-red"],
1658
+ green: ["green", "B03", "B3", "visual-green"],
1659
+ blue: ["blue", "B02", "B2", "visual-blue"],
1660
+ nir: ["nir", "nir08", "B08", "B8", "B8A"],
1661
+ swir1: ["swir16", "swir1", "B11"],
1662
+ swir2: ["swir22", "swir2", "B12"],
1663
+ rededge: ["rededge1", "rededge", "B05", "B5"]
1664
+ };
1665
+ function extractSentinelBandAssets(item) {
1666
+ const out = {};
1667
+ const assets = item.assets ?? {};
1668
+ for (const [key, asset] of Object.entries(assets)) {
1669
+ if (!asset?.href) continue;
1670
+ const bands = asset["eo:bands"];
1671
+ if (Array.isArray(bands) && bands.length >= 1) {
1672
+ const common = bands[0]?.common_name?.toLowerCase();
1673
+ if (common && isBandSlot(common)) {
1674
+ if (!out[common]) out[common] = asset.href;
1675
+ continue;
1676
+ }
1677
+ }
1678
+ for (const slot of Object.keys(BAND_KEY_FALLBACKS)) {
1679
+ if (BAND_KEY_FALLBACKS[slot].includes(key) && !out[slot]) {
1680
+ out[slot] = asset.href;
1681
+ break;
1682
+ }
1683
+ }
1684
+ }
1685
+ return out;
1686
+ }
1687
+ function isBandSlot(value) {
1688
+ return value === "red" || value === "green" || value === "blue" || value === "nir" || value === "swir1" || value === "swir2" || value === "rededge";
1689
+ }
1690
+ function hasRgbBands(map) {
1691
+ return Boolean(map.red && map.green && map.blue);
1692
+ }
1693
+ function extractRasterBandAssets(item) {
1694
+ const out = [];
1695
+ const assets = item.assets ?? {};
1696
+ for (const [key, asset] of Object.entries(assets)) {
1697
+ if (!asset?.href) continue;
1698
+ const mediaType = typeof asset.type === "string" ? asset.type : void 0;
1699
+ if (mediaType && !/^image\/(tiff|geotiff)\b/i.test(mediaType)) continue;
1700
+ const roles = Array.isArray(asset.roles) ? asset.roles : void 0;
1701
+ if (roles && (roles.includes("thumbnail") || roles.includes("overview") || roles.includes("metadata"))) {
1702
+ continue;
1703
+ }
1704
+ const eoBands = Array.isArray(asset["eo:bands"]) ? asset["eo:bands"] : void 0;
1705
+ const assetExt = asset;
1706
+ const rasterBands = Array.isArray(assetExt["raster:bands"]) ? assetExt["raster:bands"] : void 0;
1707
+ const bandCount = rasterBands?.length ?? eoBands?.length;
1708
+ if (typeof bandCount === "number" && bandCount > 1) continue;
1709
+ const commonName = eoBands?.[0]?.common_name?.toLowerCase();
1710
+ out.push({
1711
+ key,
1712
+ href: asset.href,
1713
+ commonName,
1714
+ bandCount: typeof bandCount === "number" ? bandCount : 1,
1715
+ roles,
1716
+ mediaType,
1717
+ title: typeof asset.title === "string" ? asset.title : void 0
1718
+ });
1719
+ }
1720
+ return out;
1721
+ }
1722
+ function resolveBandSlotAssetKey(assets, slot) {
1723
+ const byCommon = assets.find((a) => a.commonName === slot);
1724
+ if (byCommon) return byCommon.key;
1725
+ const fallbacks = BAND_KEY_FALLBACKS[slot] ?? [];
1726
+ const byKey = assets.find((a) => fallbacks.includes(a.key));
1727
+ return byKey?.key;
1728
+ }
1729
+ function resolvePresetComposite(assets, composite) {
1730
+ const r = resolveBandSlotAssetKey(assets, composite.r);
1731
+ const g = resolveBandSlotAssetKey(assets, composite.g);
1732
+ const b = resolveBandSlotAssetKey(assets, composite.b);
1733
+ if (!r || !g || !b) return null;
1734
+ return { r, g, b };
1735
+ }
1736
+ function hasCompositableBands(assets) {
1737
+ return assets.length >= 3;
1738
+ }
1739
+ function extractMosaicAssets(item) {
1740
+ const out = [];
1741
+ const assets = item.assets ?? {};
1742
+ for (const [key, asset] of Object.entries(assets)) {
1743
+ if (!asset?.href) continue;
1744
+ const mediaType = typeof asset.type === "string" ? asset.type : void 0;
1745
+ if (mediaType && !/^image\/(tiff|geotiff)\b/i.test(mediaType)) continue;
1746
+ const roles = Array.isArray(asset.roles) ? asset.roles : void 0;
1747
+ if (roles && (roles.includes("thumbnail") || roles.includes("overview") || roles.includes("metadata"))) {
1748
+ continue;
1749
+ }
1750
+ const eoBands = Array.isArray(asset["eo:bands"]) ? asset["eo:bands"] : void 0;
1751
+ const assetExt = asset;
1752
+ const rasterBands = Array.isArray(assetExt["raster:bands"]) ? assetExt["raster:bands"] : void 0;
1753
+ const bandCount = rasterBands?.length ?? eoBands?.length;
1754
+ const commonName = eoBands?.[0]?.common_name?.toLowerCase();
1755
+ out.push({
1756
+ key,
1757
+ href: asset.href,
1758
+ commonName,
1759
+ bandCount: typeof bandCount === "number" ? bandCount : void 0,
1760
+ roles,
1761
+ mediaType,
1762
+ title: typeof asset.title === "string" ? asset.title : void 0
1763
+ });
1764
+ }
1765
+ return out;
1766
+ }
1767
+
1768
+ // src/channel-composite.ts
1769
+ var PRESETS = [
1770
+ {
1771
+ id: "natural-color",
1772
+ labelKey: "map.multiCogPreset.trueColor",
1773
+ slots: { r: "red", g: "green", b: "blue" }
1774
+ },
1775
+ {
1776
+ id: "false-color-ir",
1777
+ labelKey: "map.multiCogPreset.falseColorIR",
1778
+ slots: { r: "nir", g: "red", b: "green" }
1779
+ },
1780
+ {
1781
+ id: "swir",
1782
+ labelKey: "map.multiCogPreset.swir",
1783
+ slots: { r: "swir2", g: "swir1", b: "red" }
1784
+ },
1785
+ {
1786
+ id: "vegetation",
1787
+ labelKey: "map.multiCogPreset.vegetation",
1788
+ slots: { r: "nir", g: "swir1", b: "red" }
1789
+ },
1790
+ {
1791
+ id: "agriculture",
1792
+ labelKey: "map.multiCogPreset.agriculture",
1793
+ slots: { r: "swir1", g: "nir", b: "blue" }
1794
+ }
1795
+ ];
1796
+ function toRasterBandAssets(assets) {
1797
+ return assets.map((a) => ({
1798
+ key: a.key,
1799
+ href: a.href,
1800
+ commonName: a.eoCommon[0],
1801
+ bandCount: a.bandCount,
1802
+ roles: a.roles,
1803
+ mediaType: a.mediaType,
1804
+ title: a.title
1805
+ }));
1806
+ }
1807
+ function availablePresets(assets) {
1808
+ const rba = toRasterBandAssets(assets);
1809
+ return PRESETS.filter((p) => resolvePresetComposite(rba, p.slots) !== null);
1810
+ }
1811
+ function applyPreset(assets, preset) {
1812
+ const rba = toRasterBandAssets(assets);
1813
+ const r = resolvePresetComposite(rba, preset.slots);
1814
+ if (!r) return null;
1815
+ return {
1816
+ r: { assetKey: r.r, bandIndex: 0 },
1817
+ g: { assetKey: r.g, bandIndex: 0 },
1818
+ b: { assetKey: r.b, bandIndex: 0 }
1819
+ };
1820
+ }
1821
+ function presetMatchesComposite(preset, c, assets) {
1822
+ const resolved = applyPreset(assets, preset);
1823
+ if (!resolved) return false;
1824
+ return resolved.r.assetKey === c.r.assetKey && resolved.g.assetKey === c.g.assetKey && resolved.b.assetKey === c.b.assetKey && c.r.bandIndex === 0 && c.g.bandIndex === 0 && c.b.bandIndex === 0;
1825
+ }
1826
+ function compositeFromUrl(params, assets) {
1827
+ const r = params.get("r");
1828
+ const g = params.get("g");
1829
+ const b = params.get("b");
1830
+ if (!r || !g || !b) return null;
1831
+ const known = new Map(assets.map((a2) => [a2.key, a2]));
1832
+ const ra = known.get(r);
1833
+ const ga = known.get(g);
1834
+ const ba = known.get(b);
1835
+ if (!ra || !ga || !ba) return null;
1836
+ const out = {
1837
+ r: { assetKey: r, bandIndex: clampBand(params.get("band_r"), ra.bandCount) },
1838
+ g: { assetKey: g, bandIndex: clampBand(params.get("band_g"), ga.bandCount) },
1839
+ b: { assetKey: b, bandIndex: clampBand(params.get("band_b"), ba.bandCount) }
1840
+ };
1841
+ const a = params.get("a");
1842
+ if (a) {
1843
+ const aa = known.get(a);
1844
+ if (aa) out.a = { assetKey: a, bandIndex: clampBand(params.get("band_a"), aa.bandCount) };
1845
+ }
1846
+ return out;
1847
+ }
1848
+ function compositeToUrl(c, presetId) {
1849
+ const p = new URLSearchParams();
1850
+ p.set("r", c.r.assetKey);
1851
+ p.set("g", c.g.assetKey);
1852
+ p.set("b", c.b.assetKey);
1853
+ if (c.r.bandIndex !== 0) p.set("band_r", String(c.r.bandIndex));
1854
+ if (c.g.bandIndex !== 0) p.set("band_g", String(c.g.bandIndex));
1855
+ if (c.b.bandIndex !== 0) p.set("band_b", String(c.b.bandIndex));
1856
+ if (c.a) {
1857
+ p.set("a", c.a.assetKey);
1858
+ if (c.a.bandIndex !== 0) p.set("band_a", String(c.a.bandIndex));
1859
+ }
1860
+ if (presetId) p.set("preset", presetId);
1861
+ return p;
1862
+ }
1863
+ function clampBand(raw, bandCount) {
1864
+ if (!raw) return 0;
1865
+ const n = Number(raw);
1866
+ if (!Number.isFinite(n)) return 0;
1867
+ const i = Math.floor(n);
1868
+ if (i < 0) return 0;
1869
+ if (i >= bandCount) return Math.max(0, bandCount - 1);
1870
+ return i;
1871
+ }
1872
+
1873
+ // src/clipboard.ts
1874
+ async function copyToClipboard(text, onFeedback) {
1875
+ try {
1876
+ await navigator.clipboard.writeText(text);
1877
+ onFeedback?.(true);
1878
+ setTimeout(() => onFeedback?.(false), COPY_FEEDBACK_MS);
1879
+ return true;
1880
+ } catch {
1881
+ return false;
1882
+ }
1883
+ }
1884
+ function wireCodeCopyButtons(root, selector) {
1885
+ for (const btn of root.querySelectorAll(selector)) {
1886
+ btn.addEventListener("click", async () => {
1887
+ const code = decodeURIComponent(btn.dataset.code ?? "");
1888
+ try {
1889
+ await navigator.clipboard.writeText(code);
1890
+ btn.classList.add("copied");
1891
+ setTimeout(() => btn.classList.remove("copied"), COPY_FEEDBACK_MS);
1892
+ } catch {
1893
+ }
1894
+ });
1895
+ }
1896
+ }
1897
+
1898
+ // src/cloud-url.ts
1428
1899
  var AWS_REGION_RE = /^(us|eu|ap|sa|ca|me|af|il)-(north|south|east|west|central|northeast|southeast|northwest|southwest)-\d+/;
1429
1900
  function getNativeScheme(provider) {
1430
1901
  const def = PROVIDERS[provider];
@@ -1439,15 +1910,15 @@ function safeDecodeURIComponent(s) {
1439
1910
  }
1440
1911
  }
1441
1912
  function resolveCloudUrl(url) {
1442
- const s3Match = url.match(/^s3[an]?:\/\/([^/]+)\/?(.*)$/);
1913
+ const s3Match = url.match(/^s3[an]?:\/\/([^/]+)(?:\/(.*))?$/);
1443
1914
  if (s3Match) {
1444
1915
  const [, bucket, key] = s3Match;
1445
1916
  const regionMatch = bucket.match(AWS_REGION_RE);
1446
- const region = regionMatch ? regionMatch[0] : "us-east-1";
1917
+ const region = regionMatch ? regionMatch[0] : DEFAULT_AWS_REGION;
1447
1918
  const base = buildProviderBaseUrl("s3", "", bucket, region);
1448
1919
  return key ? `${base}/${key}` : base;
1449
1920
  }
1450
- const gcsMatch = url.match(/^gcs?:\/\/([^/]+)\/?(.*)$/);
1921
+ const gcsMatch = url.match(/^gcs?:\/\/([^/]+)(?:\/(.*))?$/);
1451
1922
  if (gcsMatch) {
1452
1923
  const [, bucket, key] = gcsMatch;
1453
1924
  const base = buildProviderBaseUrl("gcs", "", bucket, "");
@@ -1456,7 +1927,121 @@ function resolveCloudUrl(url) {
1456
1927
  return url;
1457
1928
  }
1458
1929
 
1459
- // ../../src/lib/utils/cog-pure.ts
1930
+ // src/cog-asset.ts
1931
+ var TIFF_MEDIA = /^image\/(tiff|geotiff)\b/i;
1932
+ var NON_DATA_ROLES = /* @__PURE__ */ new Set(["thumbnail", "overview", "metadata"]);
1933
+ function extractCogAssets(item) {
1934
+ const out = [];
1935
+ const assets = item.assets ?? {};
1936
+ const props = item.properties ?? {};
1937
+ const itemBands = Array.isArray(props.bands) ? props.bands : void 0;
1938
+ for (const [key, asset] of Object.entries(assets)) {
1939
+ if (!asset?.href) continue;
1940
+ const mediaType = typeof asset.type === "string" ? asset.type : void 0;
1941
+ if (mediaType && !TIFF_MEDIA.test(mediaType)) continue;
1942
+ const roles = Array.isArray(asset.roles) ? asset.roles : [];
1943
+ if (roles.some((r) => NON_DATA_ROLES.has(r))) continue;
1944
+ const eoBands = Array.isArray(asset["eo:bands"]) ? asset["eo:bands"] : void 0;
1945
+ const assetExt = asset;
1946
+ const rasterBands = Array.isArray(assetExt["raster:bands"]) ? assetExt["raster:bands"] : void 0;
1947
+ const assetBands11 = Array.isArray(assetExt.bands) ? assetExt.bands : void 0;
1948
+ const bandCount = assetBands11?.length ?? rasterBands?.length ?? eoBands?.length ?? itemBands?.length;
1949
+ const bandCountKnown = typeof bandCount === "number" && bandCount > 0;
1950
+ const commonSource = eoBands ?? assetBands11 ?? itemBands;
1951
+ const eoCommon = commonSource ? commonSource.map((b) => {
1952
+ const c = b?.common_name;
1953
+ return typeof c === "string" ? c.toLowerCase() : "";
1954
+ }) : [];
1955
+ const dtypeSource = rasterBands ?? assetBands11 ?? itemBands;
1956
+ const dtype = dtypeSource?.[0]?.data_type;
1957
+ out.push({
1958
+ key,
1959
+ href: asset.href,
1960
+ bandCount: bandCountKnown ? bandCount : 1,
1961
+ bandCountKnown,
1962
+ dtype: typeof dtype === "string" ? dtype : void 0,
1963
+ eoCommon,
1964
+ roles,
1965
+ title: typeof asset.title === "string" ? asset.title : void 0,
1966
+ mediaType
1967
+ });
1968
+ }
1969
+ return out;
1970
+ }
1971
+ function syntheticSelfAsset(href, probedBandCount) {
1972
+ const known = typeof probedBandCount === "number" && probedBandCount > 0;
1973
+ return {
1974
+ key: "self",
1975
+ href,
1976
+ bandCount: known ? probedBandCount : 1,
1977
+ bandCountKnown: known,
1978
+ eoCommon: [],
1979
+ roles: []
1980
+ };
1981
+ }
1982
+ function pickNaturalColorComposite(assets) {
1983
+ if (assets.length === 0) return null;
1984
+ for (const a of assets) {
1985
+ if (a.bandCount === 3 && (a.roles.includes("visual") || hasRgbInEoCommon(a.eoCommon))) {
1986
+ return {
1987
+ composite: {
1988
+ r: { assetKey: a.key, bandIndex: indexOfCommon(a.eoCommon, "red", 0) },
1989
+ g: { assetKey: a.key, bandIndex: indexOfCommon(a.eoCommon, "green", 1) },
1990
+ b: { assetKey: a.key, bandIndex: indexOfCommon(a.eoCommon, "blue", 2) }
1991
+ },
1992
+ source: "visual-asset"
1993
+ };
1994
+ }
1995
+ }
1996
+ const red = assets.find((a) => a.eoCommon[0] === "red");
1997
+ const green = assets.find((a) => a.eoCommon[0] === "green");
1998
+ const blue = assets.find((a) => a.eoCommon[0] === "blue");
1999
+ if (red && green && blue) {
2000
+ return {
2001
+ composite: {
2002
+ r: { assetKey: red.key, bandIndex: 0 },
2003
+ g: { assetKey: green.key, bandIndex: 0 },
2004
+ b: { assetKey: blue.key, bandIndex: 0 }
2005
+ },
2006
+ source: "rgb-bands"
2007
+ };
2008
+ }
2009
+ if (assets.length >= 3) {
2010
+ return {
2011
+ composite: {
2012
+ r: { assetKey: assets[0].key, bandIndex: 0 },
2013
+ g: { assetKey: assets[1].key, bandIndex: 0 },
2014
+ b: { assetKey: assets[2].key, bandIndex: 0 }
2015
+ },
2016
+ source: "fallback"
2017
+ };
2018
+ }
2019
+ const only = assets[0];
2020
+ const last = Math.max(0, only.bandCount - 1);
2021
+ return {
2022
+ composite: {
2023
+ r: { assetKey: only.key, bandIndex: 0 },
2024
+ g: { assetKey: only.key, bandIndex: Math.min(1, last) },
2025
+ b: { assetKey: only.key, bandIndex: Math.min(2, last) }
2026
+ },
2027
+ source: "fallback"
2028
+ };
2029
+ }
2030
+ function hasRgbInEoCommon(eo) {
2031
+ return eo.includes("red") && eo.includes("green") && eo.includes("blue");
2032
+ }
2033
+ function indexOfCommon(eo, name, fallback) {
2034
+ const i = eo.indexOf(name);
2035
+ return i >= 0 ? i : fallback;
2036
+ }
2037
+ function isSingleAssetComposite(c) {
2038
+ return c.r.assetKey === c.g.assetKey && c.g.assetKey === c.b.assetKey;
2039
+ }
2040
+ function allChannelsBand0(c) {
2041
+ return c.r.bandIndex === 0 && c.g.bandIndex === 0 && c.b.bandIndex === 0;
2042
+ }
2043
+
2044
+ // src/cog-info.ts
1460
2045
  var SF_LABELS = {
1461
2046
  1: "uint",
1462
2047
  2: "int",
@@ -1480,7 +2065,7 @@ function buildDataTypeLabel(sampleFormat, bitsPerSample) {
1480
2065
  return `${SF_LABELS[sampleFormat] ?? `sf${sampleFormat}`}${bitsPerSample ?? ""}`;
1481
2066
  }
1482
2067
 
1483
- // ../../src/lib/utils/column-types.ts
2068
+ // src/column-types.ts
1484
2069
  var NUMBER_TYPES = [
1485
2070
  "TINYINT",
1486
2071
  "SMALLINT",
@@ -1534,7 +2119,9 @@ var BINARY_TYPES = ["BLOB", "BYTEA", "BINARY", "VARBINARY"];
1534
2119
  var JSON_TYPES = ["JSON", "JSONB"];
1535
2120
  function classifyType(duckdbType) {
1536
2121
  const upper = duckdbType.toUpperCase().trim();
1537
- const base = upper.replace(/\(.*\)/, "").trim();
2122
+ const openIdx = upper.indexOf("(");
2123
+ const closeIdx = upper.lastIndexOf(")");
2124
+ const base = (openIdx >= 0 && closeIdx > openIdx ? upper.slice(0, openIdx) + upper.slice(closeIdx + 1) : upper).trim();
1538
2125
  if (NUMBER_TYPES.includes(base)) return "number";
1539
2126
  if (STRING_TYPES.includes(base)) return "string";
1540
2127
  if (DATE_TYPES.includes(base)) return "date";
@@ -1596,13 +2183,96 @@ function typeLabel(category) {
1596
2183
  return TYPE_LABELS[category];
1597
2184
  }
1598
2185
 
1599
- // ../../src/lib/utils/error.ts
2186
+ // src/connection-identity.ts
2187
+ var DEFAULT_PORTS = {
2188
+ "https:": "443",
2189
+ "http:": "80"
2190
+ };
2191
+ var SLASH = 47;
2192
+ function stripTrailingSlashes(s) {
2193
+ let end = s.length;
2194
+ while (end > 0 && s.charCodeAt(end - 1) === SLASH) end--;
2195
+ return s.slice(0, end);
2196
+ }
2197
+ function stripEdgeSlashes(s) {
2198
+ let start = 0;
2199
+ let end = s.length;
2200
+ while (start < end && s.charCodeAt(start) === SLASH) start++;
2201
+ while (end > start && s.charCodeAt(end - 1) === SLASH) end--;
2202
+ return s.slice(start, end);
2203
+ }
2204
+ function normalizeEndpoint(raw) {
2205
+ if (!raw) return "";
2206
+ const trimmed = raw.trim();
2207
+ if (!trimmed) return "";
2208
+ try {
2209
+ const url = new URL(trimmed);
2210
+ const scheme = url.protocol.toLowerCase();
2211
+ const host = url.hostname.toLowerCase();
2212
+ const defaultPort = DEFAULT_PORTS[scheme] ?? "";
2213
+ const port = url.port && url.port !== defaultPort ? `:${url.port}` : "";
2214
+ const path = stripTrailingSlashes(url.pathname);
2215
+ return `${scheme}//${host}${port}${path}`;
2216
+ } catch {
2217
+ return stripTrailingSlashes(trimmed.toLowerCase());
2218
+ }
2219
+ }
2220
+ function normalizeProvider(provider) {
2221
+ if (!provider) return "s3";
2222
+ const p = provider.trim().toLowerCase();
2223
+ if (!p || p === "unknown") return "s3";
2224
+ return p;
2225
+ }
2226
+ function normalizeBucket(bucket) {
2227
+ return stripEdgeSlashes((bucket ?? "").trim());
2228
+ }
2229
+ function normalizeRegion(region) {
2230
+ return (region ?? "").trim().toLowerCase();
2231
+ }
2232
+ function connectionIdentityKey(input) {
2233
+ const provider = normalizeProvider(input.provider);
2234
+ const bucket = normalizeBucket(input.bucket);
2235
+ if (!bucket) return "";
2236
+ const endpoint = normalizeEndpoint(input.endpoint);
2237
+ const region = normalizeRegion(input.region);
2238
+ if (provider === "azure") return `azure|${endpoint}|${bucket}`;
2239
+ if (provider === "gcs") return `gcs|${bucket}`;
2240
+ if (provider === "s3" && !endpoint) return `s3|${bucket}|${region}`;
2241
+ return `${provider}|${endpoint}|${bucket}`;
2242
+ }
2243
+ function isSameConnectionIdentity(a, b) {
2244
+ const key = connectionIdentityKey(a);
2245
+ return key !== "" && key === connectionIdentityKey(b);
2246
+ }
2247
+
2248
+ // src/crs.ts
2249
+ var WGS84_STRINGS = /* @__PURE__ */ new Set(["epsg:4326", "epsg:4979", DEFAULT_TARGET_CRS.toLowerCase()]);
2250
+ function isWgs84(crs) {
2251
+ if (crs === null || crs === void 0) return false;
2252
+ if (typeof crs === "number") return Number.isFinite(crs) && WGS84_CODES.has(crs);
2253
+ const s = crs.trim().toLowerCase();
2254
+ if (s.length === 0) return false;
2255
+ if (WGS84_STRINGS.has(s)) return true;
2256
+ const m = s.match(/(?:epsg:)?(\d+)/);
2257
+ return m ? WGS84_CODES.has(Number(m[1])) : false;
2258
+ }
2259
+
2260
+ // src/error.ts
2261
+ function isAbortError(err) {
2262
+ if (!err) return false;
2263
+ if (err instanceof DOMException && err.name === "AbortError") return true;
2264
+ const e = err;
2265
+ if (e.name === "AbortError") return true;
2266
+ if (typeof e.message === "string" && /\baborted?\b/i.test(e.message)) return true;
2267
+ if (e.cause && isAbortError(e.cause)) return true;
2268
+ return false;
2269
+ }
1600
2270
  function handleLoadError(err) {
1601
- if (err instanceof DOMException && err.name === "AbortError") return null;
2271
+ if (isAbortError(err)) return null;
1602
2272
  return err instanceof Error ? err.message : String(err);
1603
2273
  }
1604
2274
 
1605
- // ../../src/lib/utils/format.ts
2275
+ // src/format.ts
1606
2276
  function formatFileSize(bytes) {
1607
2277
  if (bytes < 0) return "0 B";
1608
2278
  if (bytes === 0) return "0 B";
@@ -1648,7 +2318,7 @@ function formatValue(value) {
1648
2318
  return String(value);
1649
2319
  }
1650
2320
 
1651
- // ../../src/lib/utils/export.ts
2321
+ // src/export.ts
1652
2322
  function triggerDownload(content, filename, mimeType) {
1653
2323
  const blob = new Blob([content], { type: mimeType });
1654
2324
  const url = URL.createObjectURL(blob);
@@ -1709,7 +2379,7 @@ function exportToJson(columns, rows, filename) {
1709
2379
  );
1710
2380
  }
1711
2381
 
1712
- // ../../src/lib/utils/file-sort.ts
2382
+ // src/file-sort.ts
1713
2383
  function sortFileEntries(entries, config) {
1714
2384
  const sorted = [...entries];
1715
2385
  const dir = config.direction === "asc" ? 1 : -1;
@@ -2314,7 +2984,49 @@ function buildGeoArrowTables(wkbArrays, attributes, knownGeomType) {
2314
2984
  return results;
2315
2985
  }
2316
2986
 
2317
- // ../../src/lib/utils/hex.ts
2987
+ // src/geometry-type.ts
2988
+ var GEOMETRY_PREFIX = /^GEOMETRY(?:\s*\(([^)]*)\))?/i;
2989
+ function stripCrsQuotes(inner) {
2990
+ let crs = inner.trim();
2991
+ if (crs.length >= 2 && crs.startsWith("'") && crs.endsWith("'")) {
2992
+ crs = crs.slice(1, -1).trim();
2993
+ }
2994
+ return crs || null;
2995
+ }
2996
+ function parseGeometryTypeCrs(typeStr) {
2997
+ if (!typeStr) return { isGeometry: false, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
2998
+ const match = typeStr.match(GEOMETRY_PREFIX);
2999
+ if (!match) return { isGeometry: false, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
3000
+ const rawCrs = match[1] != null ? stripCrsQuotes(match[1]) : null;
3001
+ if (!rawCrs) return { isGeometry: true, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
3002
+ return {
3003
+ isGeometry: true,
3004
+ hasCrs: true,
3005
+ rawCrs,
3006
+ nonWgs84Crs: isWgs84Crs(rawCrs) ? null : rawCrs
3007
+ };
3008
+ }
3009
+ function isWgs84Crs(crs) {
3010
+ if (!crs) return true;
3011
+ const trimmed = crs.trim();
3012
+ if (trimmed === "OGC:CRS84" || trimmed === "OGC:CRS83") return true;
3013
+ const epsgMatch = trimmed.match(/^EPSG:(\d+)$/i);
3014
+ if (epsgMatch && WGS84_CODES.has(Number(epsgMatch[1]))) return true;
3015
+ return false;
3016
+ }
3017
+ function buildTransformExpr(innerExpr, sourceType, sourceCrs, targetCrs) {
3018
+ const info = parseGeometryTypeCrs(sourceType);
3019
+ if (info.hasCrs) {
3020
+ return `ST_Transform(${innerExpr}, '${targetCrs}')`;
3021
+ }
3022
+ return `ST_Transform(${innerExpr}, '${sourceCrs}', '${targetCrs}')`;
3023
+ }
3024
+ function wrapWkbWithCrs(wkbExpr, sourceCrs) {
3025
+ if (!sourceCrs) return `ST_GeomFromWKB(${wkbExpr})`;
3026
+ return `ST_SetCRS(ST_GeomFromWKB(${wkbExpr}), '${sourceCrs}')`;
3027
+ }
3028
+
3029
+ // src/hex.ts
2318
3030
  function generateHexDump(data, bytesPerRow = 16) {
2319
3031
  const rows = [];
2320
3032
  for (let i = 0; i < data.length; i += bytesPerRow) {
@@ -2338,458 +3050,233 @@ function generateHexDump(data, bytesPerRow = 16) {
2338
3050
  return rows;
2339
3051
  }
2340
3052
 
2341
- // ../../src/lib/utils/local-storage.ts
2342
- function loadFromStorage(key, defaultValue) {
2343
- if (typeof window === "undefined") return defaultValue;
2344
- try {
2345
- const raw = localStorage.getItem(key);
2346
- if (raw) return JSON.parse(raw);
2347
- } catch {
2348
- }
2349
- return defaultValue;
3053
+ // src/stac-storage-extension.ts
3054
+ function emptyStorageHints() {
3055
+ return { platform: null, region: null, requesterPays: false, endpoint: null };
2350
3056
  }
2351
- function persistToStorage(key, value) {
2352
- if (typeof window === "undefined") return;
2353
- try {
2354
- localStorage.setItem(key, JSON.stringify(value));
2355
- } catch {
3057
+ function detectStorageExtensionVersion(item) {
3058
+ const exts = item.stac_extensions;
3059
+ if (!Array.isArray(exts)) return null;
3060
+ for (const raw of exts) {
3061
+ if (typeof raw !== "string") continue;
3062
+ if (!raw.includes("stac-extensions.github.io/storage")) continue;
3063
+ const trimmed = raw.endsWith("/schema.json") ? raw.slice(0, -"/schema.json".length) : raw;
3064
+ const last = trimmed.slice(trimmed.lastIndexOf("/") + 1);
3065
+ const version = last.startsWith("v") ? last.slice(1) : last;
3066
+ if (version === "1.0.0" || version === "2.0.0") return version;
3067
+ const major = version.split(".")[0];
3068
+ if (major === "1") return "1.0.0";
3069
+ if (major === "2") return "2.0.0";
2356
3070
  }
3071
+ return null;
2357
3072
  }
2358
-
2359
- // ../../src/lib/utils/markdown-sql.ts
2360
- async function parseMarkdownDocument(markdown) {
2361
- let frontmatter = {};
2362
- let content = markdown;
2363
- const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---\n/);
2364
- if (fmMatch) {
2365
- try {
2366
- const { default: YAML } = await import('yaml');
2367
- frontmatter = YAML.parse(fmMatch[1]) || {};
2368
- } catch {
3073
+ function extractStorageHints(item, assetKey) {
3074
+ const version = detectStorageExtensionVersion(item);
3075
+ if (version === "1.0.0") return extractV1Hints(item, assetKey);
3076
+ if (version === "2.0.0") return extractV2Hints(item, assetKey);
3077
+ return emptyStorageHints();
3078
+ }
3079
+ function extractV1Hints(item, assetKey) {
3080
+ const props = item.properties ?? {};
3081
+ const asset = assetKey && item.assets && item.assets[assetKey] ? item.assets[assetKey] : null;
3082
+ const platformRaw = pickFirstString([asset?.["storage:platform"], props["storage:platform"]]);
3083
+ const region = pickFirstString([asset?.["storage:region"], props["storage:region"]]);
3084
+ const requesterPays = pickFirstBoolean([
3085
+ asset?.["storage:requester_pays"],
3086
+ props["storage:requester_pays"]
3087
+ ]);
3088
+ return {
3089
+ platform: platformRaw ? platformRaw.toUpperCase() : null,
3090
+ region: region ?? null,
3091
+ requesterPays: requesterPays === true,
3092
+ endpoint: null
3093
+ };
3094
+ }
3095
+ function extractV2Hints(item, assetKey) {
3096
+ const props = item.properties ?? {};
3097
+ const schemes = props["storage:schemes"] ?? {};
3098
+ if (!schemes || typeof schemes !== "object") return emptyStorageHints();
3099
+ const refs = collectAssetRefs(item, assetKey);
3100
+ const scheme = pickFirstScheme(schemes, refs);
3101
+ if (!scheme) return emptyStorageHints();
3102
+ const storeType = typeof scheme.type === "string" ? scheme.type : "";
3103
+ const platform = typeof scheme.platform === "string" ? scheme.platform : null;
3104
+ const region = typeof scheme.region === "string" ? scheme.region : null;
3105
+ const requesterPays = scheme.requester_pays === true;
3106
+ let endpoint = null;
3107
+ if (storeType === "custom-s3") {
3108
+ if (typeof scheme.endpoint === "string" && scheme.endpoint && !scheme.endpoint.includes("{")) {
3109
+ endpoint = scheme.endpoint;
3110
+ } else if (platform && !platform.includes("{")) {
3111
+ endpoint = platform;
2369
3112
  }
2370
- content = markdown.slice(fmMatch[0].length);
2371
3113
  }
2372
- const sqlBlocks = [];
2373
- const lines = content.split("\n");
2374
- let i = 0;
2375
- while (i < lines.length) {
2376
- const line = lines[i];
2377
- const match = line.match(/^```sql\s+(\w[\w-]*)\s*$/);
2378
- if (match) {
2379
- const name = match[1];
2380
- const startLine = i;
2381
- const sqlLines = [];
2382
- i++;
2383
- while (i < lines.length && lines[i] !== "```") {
2384
- sqlLines.push(lines[i]);
2385
- i++;
2386
- }
2387
- sqlBlocks.push({
2388
- name,
2389
- sql: sqlLines.join("\n"),
2390
- startLine,
2391
- endLine: i
2392
- });
2393
- }
2394
- i++;
3114
+ return {
3115
+ platform: platform ? platform.toUpperCase() : null,
3116
+ region,
3117
+ requesterPays,
3118
+ endpoint
3119
+ };
3120
+ }
3121
+ function collectAssetRefs(item, assetKey) {
3122
+ const assets = item.assets ?? {};
3123
+ if (assetKey) {
3124
+ const a = assets[assetKey];
3125
+ const refs = a?.["storage:refs"];
3126
+ if (Array.isArray(refs)) return refs.filter((r) => typeof r === "string");
3127
+ return [];
2395
3128
  }
2396
- return { frontmatter, content, sqlBlocks };
3129
+ const out = [];
3130
+ for (const a of Object.values(assets)) {
3131
+ if (!a || typeof a !== "object") continue;
3132
+ const refs = a["storage:refs"];
3133
+ if (!Array.isArray(refs)) continue;
3134
+ for (const r of refs) if (typeof r === "string") out.push(r);
3135
+ }
3136
+ return out;
2397
3137
  }
2398
- function interpolateTemplates(text, queryResults) {
2399
- return text.replace(/\{(\w+)\.rows\[(\d+)\]\.(\w+)\}/g, (match, queryName, rowIdx, colName) => {
2400
- const rows = queryResults.get(queryName);
2401
- if (!rows) return match;
2402
- const row = rows[parseInt(rowIdx, 10)];
2403
- if (!row) return match;
2404
- const value = row[colName];
2405
- return value !== void 0 ? String(value) : match;
2406
- });
3138
+ function pickFirstScheme(schemes, refs) {
3139
+ for (const r of refs) {
3140
+ const s = schemes[r];
3141
+ if (s && typeof s === "object") return s;
3142
+ }
3143
+ return null;
2407
3144
  }
2408
- function markSqlBlocks(content) {
2409
- return content.replace(
2410
- /```sql\s+(\w[\w-]*)\s*\n([\s\S]*?)```/g,
2411
- (_, name) => `<div data-sql-block="${name}"></div>`
2412
- );
3145
+ function pickFirstString(candidates) {
3146
+ for (const c of candidates) {
3147
+ if (typeof c === "string" && c.length > 0) return c;
3148
+ }
3149
+ return null;
3150
+ }
3151
+ function pickFirstBoolean(candidates) {
3152
+ for (const c of candidates) {
3153
+ if (typeof c === "boolean") return c;
3154
+ }
3155
+ return null;
3156
+ }
3157
+ function applyStorageHintsToConnection(conn, hints) {
3158
+ const out = { ...conn };
3159
+ if (hints.region && !out.region) {
3160
+ out.region = hints.region;
3161
+ }
3162
+ if (hints.endpoint && !out.endpoint) {
3163
+ out.endpoint = hints.endpoint;
3164
+ }
3165
+ return out;
2413
3166
  }
2414
3167
 
2415
- // ../../src/lib/utils/parquet-metadata.ts
2416
- function mapParquetType(col) {
2417
- const lt = col.logical_type;
2418
- if (lt) {
2419
- if (lt.type === "GEOMETRY" || lt.type === "GEOGRAPHY") return "GEOMETRY";
2420
- if (lt.type === "STRING" || lt.type === "UTF8") return "VARCHAR";
2421
- if (lt.type === "JSON") return "JSON";
2422
- if (lt.type === "UUID") return "UUID";
2423
- if (lt.type === "ENUM") return "VARCHAR";
2424
- if (lt.type === "INT" || lt.type === "INTEGER") {
2425
- const bits = lt.bitWidth ?? 32;
2426
- const signed = lt.isSigned !== false;
2427
- if (bits <= 8) return signed ? "TINYINT" : "UTINYINT";
2428
- if (bits <= 16) return signed ? "SMALLINT" : "USMALLINT";
2429
- if (bits <= 32) return signed ? "INTEGER" : "UINTEGER";
2430
- return signed ? "BIGINT" : "UBIGINT";
3168
+ // src/storage-url.ts
3169
+ function buildSchemeMap() {
3170
+ const map = {};
3171
+ for (const [id, def] of Object.entries(PROVIDERS)) {
3172
+ for (const scheme of def.schemes) {
3173
+ const key = `${scheme}://`;
3174
+ map[key] = { provider: id, strip: key.length };
2431
3175
  }
2432
- if (lt.type === "DECIMAL") return `DECIMAL(${lt.precision ?? 18},${lt.scale ?? 0})`;
2433
- if (lt.type === "DATE") return "DATE";
2434
- if (lt.type === "TIME") return "TIME";
2435
- if (lt.type === "TIMESTAMP") return "TIMESTAMP";
2436
- if (lt.type === "BSON") return "BLOB";
2437
- }
2438
- const ct = col.converted_type;
2439
- if (ct === "UTF8") return "VARCHAR";
2440
- if (ct === "JSON") return "JSON";
2441
- if (ct === "DATE") return "DATE";
2442
- if (ct === "TIMESTAMP_MILLIS" || ct === "TIMESTAMP_MICROS") return "TIMESTAMP";
2443
- if (ct === "DECIMAL") return `DECIMAL(${col.precision ?? 18},${col.scale ?? 0})`;
2444
- if (ct === "INT_8") return "TINYINT";
2445
- if (ct === "INT_16") return "SMALLINT";
2446
- if (ct === "INT_32") return "INTEGER";
2447
- if (ct === "INT_64") return "BIGINT";
2448
- if (ct === "UINT_8") return "UTINYINT";
2449
- if (ct === "UINT_16") return "USMALLINT";
2450
- if (ct === "UINT_32") return "UINTEGER";
2451
- if (ct === "UINT_64") return "UBIGINT";
2452
- const pt = col.type;
2453
- if (pt === "BOOLEAN") return "BOOLEAN";
2454
- if (pt === "INT32") return "INTEGER";
2455
- if (pt === "INT64") return "BIGINT";
2456
- if (pt === "INT96") return "TIMESTAMP";
2457
- if (pt === "FLOAT") return "FLOAT";
2458
- if (pt === "DOUBLE") return "DOUBLE";
2459
- if (pt === "BYTE_ARRAY") return "BLOB";
2460
- if (pt === "FIXED_LEN_BYTE_ARRAY") return "BLOB";
2461
- return "VARCHAR";
2462
- }
2463
- async function readParquetMetadata(url) {
2464
- const { parquetMetadataAsync, asyncBufferFromUrl } = await import('hyparquet');
2465
- const file = await asyncBufferFromUrl({ url });
2466
- const metadata = await parquetMetadataAsync(file);
2467
- const rowCount = metadata.row_groups.reduce(
2468
- (sum, rg) => sum + Number(rg.num_rows),
2469
- 0
2470
- );
2471
- const schema = metadata.schema.slice(1).filter((col) => col.num_children === void 0).map((col) => ({
2472
- name: col.name,
2473
- type: mapParquetType(col)
2474
- }));
2475
- const topLevelColumns = [];
2476
- const root = metadata.schema[0];
2477
- const rootChildren = root?.num_children ?? 0;
2478
- let cursor = 1;
2479
- for (let i = 0; i < rootChildren && cursor < metadata.schema.length; i++) {
2480
- const el = metadata.schema[cursor];
2481
- if (el?.name) topLevelColumns.push(el.name);
2482
- cursor += countSubtree(metadata.schema, cursor);
2483
- }
2484
- let geo = null;
2485
- let legacyGeoParquet = false;
2486
- const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === "geo");
2487
- if (geoKv) {
2488
- try {
2489
- const geoJson = JSON.parse(geoKv.value ?? "");
2490
- if (geoJson.schema_version && !geoJson.version) {
2491
- legacyGeoParquet = true;
2492
- }
2493
- geo = {
2494
- primaryColumn: geoJson.primary_column ?? "geometry",
2495
- columns: {}
2496
- };
2497
- if (geoJson.columns) {
2498
- for (const [colName, colMeta] of Object.entries(geoJson.columns)) {
2499
- geo.columns[colName] = {
2500
- encoding: colMeta.encoding ?? "WKB",
2501
- geometryTypes: colMeta.geometry_types ?? [],
2502
- crs: colMeta.crs ?? null,
2503
- bbox: colMeta.bbox
2504
- };
2505
- }
2506
- }
2507
- } catch {
2508
- }
2509
- }
2510
- const createdBy = metadata.created_by ?? null;
2511
- const numRowGroups = metadata.row_groups.length;
2512
- let compression = null;
2513
- if (numRowGroups > 0 && metadata.row_groups[0].columns) {
2514
- const codecs = /* @__PURE__ */ new Set();
2515
- for (const col of metadata.row_groups[0].columns) {
2516
- const codec = col.meta_data?.codec;
2517
- if (codec) codecs.add(codec);
2518
- }
2519
- if (codecs.size === 1) {
2520
- compression = [...codecs][0];
2521
- } else if (codecs.size > 1) {
2522
- compression = [...codecs].join(", ");
2523
- }
2524
- }
2525
- return {
2526
- rowCount,
2527
- schema,
2528
- topLevelColumns,
2529
- geo,
2530
- legacyGeoParquet,
2531
- createdBy,
2532
- numRowGroups,
2533
- compression
2534
- };
2535
- }
2536
- function countSubtree(schema, start) {
2537
- const el = schema[start];
2538
- if (!el) return 0;
2539
- const n = el.num_children ?? 0;
2540
- let cursor = start + 1;
2541
- for (let i = 0; i < n; i++) {
2542
- cursor += countSubtree(schema, cursor);
2543
3176
  }
2544
- return cursor - start;
3177
+ map["swift://"] = { provider: "unknown", strip: 8 };
3178
+ return map;
2545
3179
  }
2546
- function extractEpsgFromGeoMeta(geo) {
2547
- const primaryCol = geo.columns[geo.primaryColumn];
2548
- if (!primaryCol?.crs) return null;
2549
- const crs = primaryCol.crs;
2550
- if (crs.type === "name" && crs.properties?.name?.includes("CRS84")) return null;
2551
- if (crs.id?.authority === "EPSG") {
2552
- const code = crs.id.code;
2553
- if (WGS84_CODES.has(code)) return null;
2554
- return `EPSG:${code}`;
2555
- }
2556
- return null;
3180
+ var SCHEME_MAP = buildSchemeMap();
3181
+ var SLASH2 = 47;
3182
+ function stripTrailingSlashes2(s) {
3183
+ let end = s.length;
3184
+ while (end > 0 && s.charCodeAt(end - 1) === SLASH2) end--;
3185
+ return s.slice(0, end);
2557
3186
  }
2558
- function extractGeometryTypes(geo) {
2559
- const primaryCol = geo.columns[geo.primaryColumn];
2560
- if (!primaryCol?.geometryTypes?.length) return [];
2561
- const typeMap = {
2562
- Point: "point",
2563
- LineString: "linestring",
2564
- Polygon: "polygon",
2565
- MultiPoint: "multipoint",
2566
- MultiLineString: "multilinestring",
2567
- MultiPolygon: "multipolygon"
2568
- };
2569
- const types = [];
2570
- for (const raw of primaryCol.geometryTypes) {
2571
- const base = raw.split(" ")[0];
2572
- const mapped = typeMap[base];
2573
- if (mapped && !types.includes(mapped)) types.push(mapped);
2574
- }
2575
- return types;
3187
+ function stripEdgeSlashes2(s) {
3188
+ let start = 0;
3189
+ let end = s.length;
3190
+ while (start < end && s.charCodeAt(start) === SLASH2) start++;
3191
+ while (end > start && s.charCodeAt(end - 1) === SLASH2) end--;
3192
+ return s.slice(start, end);
2576
3193
  }
2577
- function extractBounds(geo) {
2578
- const primaryCol = geo.columns[geo.primaryColumn];
2579
- if (!primaryCol?.bbox || primaryCol.bbox.length < 4) return null;
2580
- return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
3194
+ var AWS_VHOST_RE = /^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
3195
+ var AWS_PATH_RE = /^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
3196
+ var AWS_GLOBAL_HOST = "s3.amazonaws.com";
3197
+ var R2_RE = /^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/;
3198
+ var GCS_GLOBAL_HOST = "storage.googleapis.com";
3199
+ var GCS_VHOST_RE = /^(.+)\.storage\.googleapis\.com$/;
3200
+ var DO_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/;
3201
+ var DO_PATH_RE = /^([a-z0-9-]+)\.digitaloceanspaces\.com$/;
3202
+ var WASABI_RE = /^s3\.([a-z0-9-]+)\.wasabisys\.com$/;
3203
+ var B2_S3_RE = /^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/;
3204
+ var B2_NATIVE_RE = /^f[a-z0-9]+\.backblazeb2\.com$/;
3205
+ var OSS_RE = /^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/;
3206
+ var COS_RE = /^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/;
3207
+ var YANDEX_HOST = "storage.yandexcloud.net";
3208
+ var CONTABO_RE = /^([a-z0-9]+)\.contabostorage\.com$/;
3209
+ var HETZNER_RE = /^([a-z0-9]+)\.your-objectstorage\.com$/;
3210
+ var LINODE_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/;
3211
+ var LINODE_PATH_RE = /^([a-z0-9-]+)\.linodeobjects\.com$/;
3212
+ var OVH_RE = /^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/;
3213
+ var AZURE_BLOB_RE = /^([a-z0-9]+)\.blob\.core\.windows\.net$/;
3214
+ var STORJ_GATEWAY_RE = /^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
3215
+ var STORJ_LINK_RE = /^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
3216
+ function isMinioLikeHost(host) {
3217
+ return host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2581
3218
  }
2582
-
2583
- // ../../src/lib/utils/stac-geoparquet.ts
2584
- var STAC_GEOPARQUET_REQUIRED_COLUMNS = [
2585
- "stac_version",
2586
- "type",
2587
- "geometry",
2588
- "assets"
2589
- ];
2590
- function isStacGeoparquetSchema(schema) {
2591
- if (!Array.isArray(schema) || schema.length === 0) return false;
2592
- const names = new Set(schema.map((c) => c.name));
2593
- return STAC_GEOPARQUET_REQUIRED_COLUMNS.every((c) => names.has(c));
3219
+ var STAC_API_PATH_RE = /\/(collections|items|catalogs|search)(\/|\?|$)/i;
3220
+ function isKnownBucketHost(host) {
3221
+ if (!host) return false;
3222
+ if (AWS_VHOST_RE.test(host)) return true;
3223
+ if (AWS_PATH_RE.test(host)) return true;
3224
+ if (host === AWS_GLOBAL_HOST) return true;
3225
+ if (R2_RE.test(host)) return true;
3226
+ if (host === GCS_GLOBAL_HOST) return true;
3227
+ if (GCS_VHOST_RE.test(host)) return true;
3228
+ if (DO_VHOST_RE.test(host)) return true;
3229
+ if (DO_PATH_RE.test(host)) return true;
3230
+ if (WASABI_RE.test(host)) return true;
3231
+ if (B2_S3_RE.test(host)) return true;
3232
+ if (B2_NATIVE_RE.test(host)) return true;
3233
+ if (OSS_RE.test(host)) return true;
3234
+ if (COS_RE.test(host)) return true;
3235
+ if (host === YANDEX_HOST) return true;
3236
+ if (CONTABO_RE.test(host)) return true;
3237
+ if (HETZNER_RE.test(host)) return true;
3238
+ if (LINODE_VHOST_RE.test(host)) return true;
3239
+ if (LINODE_PATH_RE.test(host)) return true;
3240
+ if (OVH_RE.test(host)) return true;
3241
+ if (AZURE_BLOB_RE.test(host)) return true;
3242
+ if (STORJ_GATEWAY_RE.test(host)) return true;
3243
+ if (STORJ_LINK_RE.test(host)) return true;
3244
+ if (isMinioLikeHost(host)) return true;
3245
+ return false;
2594
3246
  }
2595
- function flattenStacBbox(bbox) {
2596
- if (!bbox) return null;
2597
- if (Array.isArray(bbox)) {
2598
- if (bbox.length < 4) return null;
2599
- const [minX, minY, maxX, maxY] = bbox;
2600
- if (![minX, minY, maxX, maxY].every((v) => Number.isFinite(v))) return null;
2601
- return [minX, minY, maxX, maxY];
2602
- }
2603
- if (typeof bbox === "object") {
2604
- const { xmin, ymin, xmax, ymax } = bbox;
2605
- if (![xmin, ymin, xmax, ymax].every((v) => Number.isFinite(v))) return null;
2606
- return [xmin, ymin, xmax, ymax];
2607
- }
2608
- return null;
3247
+ function defaultResult(defaults) {
3248
+ return {
3249
+ bucket: "",
3250
+ region: defaults.region || DEFAULT_AWS_REGION,
3251
+ endpoint: defaults.endpoint || "",
3252
+ provider: defaults.provider || "s3",
3253
+ prefix: ""
3254
+ };
2609
3255
  }
2610
- function resolveStacAssetHref(href, baseUrl) {
2611
- if (!href) return href;
2612
- if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(href)) return href;
2613
- try {
2614
- return new URL(href, baseUrl).toString();
2615
- } catch {
2616
- return href;
3256
+ function splitBucketPrefix(rest) {
3257
+ const slashIdx = rest.indexOf("/");
3258
+ if (slashIdx >= 0) {
3259
+ return {
3260
+ bucket: rest.slice(0, slashIdx),
3261
+ prefix: stripTrailingSlashes2(rest.slice(slashIdx + 1))
3262
+ };
2617
3263
  }
3264
+ return { bucket: rest, prefix: "" };
2618
3265
  }
2619
- function pickStacPrimaryAsset(assets, preferredKeys) {
2620
- if (!assets || typeof assets !== "object") return null;
2621
- const entries = Object.entries(assets).filter(
2622
- ([, a]) => a && typeof a === "object" && typeof a.href === "string"
2623
- );
2624
- if (entries.length === 0) return null;
2625
- if (preferredKeys) {
2626
- for (const key of preferredKeys) {
2627
- const match = entries.find(([k]) => k === key);
2628
- if (match) return { key: match[0], asset: match[1] };
2629
- }
2630
- }
2631
- const data = entries.find(([k]) => k === "data");
2632
- if (data) return { key: data[0], asset: data[1] };
2633
- const byRole = entries.find(([, a]) => Array.isArray(a.roles) && a.roles.includes("data"));
2634
- if (byRole) return { key: byRole[0], asset: byRole[1] };
2635
- return { key: entries[0][0], asset: entries[0][1] };
2636
- }
2637
- function normalizeAssetsField(value, baseUrl) {
2638
- if (!value || typeof value !== "object") return void 0;
2639
- const out = {};
2640
- for (const [key, raw] of Object.entries(value)) {
2641
- if (!raw || typeof raw !== "object") continue;
2642
- const asset = raw;
2643
- if (typeof asset.href !== "string" || !asset.href) continue;
2644
- out[key] = {
2645
- ...asset,
2646
- href: resolveStacAssetHref(asset.href, baseUrl)
2647
- };
2648
- }
2649
- return Object.keys(out).length > 0 ? out : void 0;
2650
- }
2651
- function normalizeLinksField(value, baseUrl) {
2652
- if (!Array.isArray(value)) return void 0;
2653
- const links = [];
2654
- for (const raw of value) {
2655
- if (!raw || typeof raw !== "object") continue;
2656
- const link = raw;
2657
- if (typeof link.href !== "string" || typeof link.rel !== "string") continue;
2658
- links.push({ ...link, href: resolveStacAssetHref(link.href, baseUrl) });
2659
- }
2660
- return links.length > 0 ? links : void 0;
2661
- }
2662
- function stacRowToItem(row, baseUrl, opts = {}) {
2663
- const { wkbParser, wkbColumn = "geom_wkb", geometryColumn = "geometry" } = opts;
2664
- let geometry = row[geometryColumn];
2665
- if (!geometry) {
2666
- const wkb = row[wkbColumn];
2667
- if (wkb && wkbParser) {
2668
- const bytes = wkb instanceof Uint8Array ? wkb : toUint8Array(wkb);
2669
- if (bytes) {
2670
- try {
2671
- geometry = wkbParser(bytes) ?? void 0;
2672
- } catch {
2673
- geometry = void 0;
2674
- }
2675
- }
2676
- }
2677
- }
2678
- const bbox = flattenStacBbox(row.bbox) ?? void 0;
2679
- const assets = normalizeAssetsField(row.assets, baseUrl);
2680
- const links = normalizeLinksField(row.links, baseUrl);
2681
- const properties = {};
2682
- for (const [key, value] of Object.entries(row)) {
2683
- if (value === null || value === void 0) continue;
2684
- if (key.startsWith("proj:") || key.startsWith("raster:") || key.startsWith("eo:")) {
2685
- properties[key] = value;
2686
- }
2687
- if (key === "datetime") {
2688
- properties.datetime = value instanceof Date ? value.toISOString() : String(value);
2689
- }
2690
- if (key === "bands") {
2691
- properties.bands = value;
2692
- }
2693
- }
2694
- const item = {
2695
- type: "Feature",
2696
- stac_version: typeof row.stac_version === "string" ? row.stac_version : "1.0.0",
2697
- id: typeof row.id === "string" ? row.id : String(row.id ?? ""),
2698
- properties
2699
- };
2700
- if (typeof row.collection === "string") item.collection = row.collection;
2701
- if (Array.isArray(row.stac_extensions)) {
2702
- item.stac_extensions = row.stac_extensions;
2703
- }
2704
- if (bbox) item.bbox = bbox;
2705
- if (geometry) item.geometry = geometry;
2706
- if (assets) item.assets = assets;
2707
- if (links) item.links = links;
2708
- return item;
2709
- }
2710
- function toUint8Array(value) {
2711
- if (value instanceof Uint8Array) return value;
2712
- if (value instanceof ArrayBuffer) return new Uint8Array(value);
2713
- if (ArrayBuffer.isView(value)) {
2714
- const view = value;
2715
- return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
2716
- }
2717
- if (Array.isArray(value)) return new Uint8Array(value);
2718
- return null;
2719
- }
2720
-
2721
- // ../../src/lib/utils/storage-url.ts
2722
- function buildSchemeMap() {
2723
- const map = {};
2724
- for (const [id, def] of Object.entries(PROVIDERS)) {
2725
- for (const scheme of def.schemes) {
2726
- const key = `${scheme}://`;
2727
- map[key] = { provider: id, strip: key.length };
2728
- }
2729
- }
2730
- map["swift://"] = { provider: "unknown", strip: 8 };
2731
- return map;
2732
- }
2733
- var SCHEME_MAP = buildSchemeMap();
2734
- var AWS_VHOST_RE = /^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2735
- var AWS_PATH_RE = /^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2736
- var AWS_GLOBAL_HOST = "s3.amazonaws.com";
2737
- var R2_RE = /^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/;
2738
- var GCS_GLOBAL_HOST = "storage.googleapis.com";
2739
- var GCS_VHOST_RE = /^(.+)\.storage\.googleapis\.com$/;
2740
- var DO_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2741
- var DO_PATH_RE = /^([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2742
- var WASABI_RE = /^s3\.([a-z0-9-]+)\.wasabisys\.com$/;
2743
- var B2_S3_RE = /^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/;
2744
- var B2_NATIVE_RE = /^f[a-z0-9]+\.backblazeb2\.com$/;
2745
- var OSS_RE = /^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/;
2746
- var COS_RE = /^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/;
2747
- var YANDEX_HOST = "storage.yandexcloud.net";
2748
- var CONTABO_RE = /^([a-z0-9]+)\.contabostorage\.com$/;
2749
- var HETZNER_RE = /^([a-z0-9]+)\.your-objectstorage\.com$/;
2750
- var LINODE_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/;
2751
- var LINODE_PATH_RE = /^([a-z0-9-]+)\.linodeobjects\.com$/;
2752
- var OVH_RE = /^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/;
2753
- var AZURE_BLOB_RE = /^([a-z0-9]+)\.blob\.core\.windows\.net$/;
2754
- var STORJ_GATEWAY_RE = /^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2755
- var STORJ_LINK_RE = /^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2756
- function isMinioLikeHost(host) {
2757
- return host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2758
- }
2759
- var STAC_API_PATH_RE = /\/(collections|items|catalogs|search)(\/|\?|$)/i;
2760
- function defaultResult(defaults) {
2761
- return {
2762
- bucket: "",
2763
- region: defaults.region || "us-east-1",
2764
- endpoint: defaults.endpoint || "",
2765
- provider: defaults.provider || "s3",
2766
- prefix: ""
2767
- };
2768
- }
2769
- function splitBucketPrefix(rest) {
2770
- const slashIdx = rest.indexOf("/");
2771
- if (slashIdx >= 0) {
2772
- return {
2773
- bucket: rest.slice(0, slashIdx),
2774
- prefix: rest.slice(slashIdx + 1).replace(/\/+$/, "")
2775
- };
2776
- }
2777
- return { bucket: rest, prefix: "" };
2778
- }
2779
- function parseStorageUrl(input, defaults = {}) {
2780
- const trimmed = input.trim();
2781
- const lower = trimmed.toLowerCase();
2782
- for (const [scheme, { provider, strip }] of Object.entries(SCHEME_MAP)) {
2783
- if (lower.startsWith(scheme)) {
2784
- const rest = trimmed.slice(strip);
2785
- const { bucket, prefix } = splitBucketPrefix(rest);
2786
- return {
2787
- bucket,
2788
- region: defaults.region || "us-east-1",
2789
- endpoint: defaults.endpoint || "",
2790
- provider,
2791
- prefix
2792
- };
3266
+ function parseStorageUrl(input, defaults = {}) {
3267
+ const trimmed = input.trim();
3268
+ const lower = trimmed.toLowerCase();
3269
+ for (const [scheme, { provider, strip }] of Object.entries(SCHEME_MAP)) {
3270
+ if (lower.startsWith(scheme)) {
3271
+ const rest = trimmed.slice(strip);
3272
+ const { bucket, prefix } = splitBucketPrefix(rest);
3273
+ return {
3274
+ bucket,
3275
+ region: defaults.region || DEFAULT_AWS_REGION,
3276
+ endpoint: defaults.endpoint || "",
3277
+ provider,
3278
+ prefix
3279
+ };
2793
3280
  }
2794
3281
  }
2795
3282
  if (lower.startsWith("http://") || lower.startsWith("https://")) {
@@ -2820,7 +3307,7 @@ function parseStorageUrl(input, defaults = {}) {
2820
3307
  if (host === AWS_GLOBAL_HOST && pathParts.length > 0) {
2821
3308
  return {
2822
3309
  bucket: pathParts[0],
2823
- region: defaults.region || "us-east-1",
3310
+ region: defaults.region || DEFAULT_AWS_REGION,
2824
3311
  endpoint: "",
2825
3312
  provider: "s3",
2826
3313
  prefix: pathParts.slice(1).join("/")
@@ -2984,96 +3471,2063 @@ function parseStorageUrl(input, defaults = {}) {
2984
3471
  prefix: pathParts.slice(1).join("/")
2985
3472
  };
2986
3473
  }
2987
- if (isMinioLikeHost(host) && pathParts.length > 0) {
2988
- return {
2989
- bucket: pathParts[0],
2990
- region: defaults.region || "us-east-1",
2991
- endpoint: `${url.protocol}//${url.host}`,
2992
- provider: "minio",
2993
- prefix: pathParts.slice(1).join("/")
2994
- };
3474
+ if (isMinioLikeHost(host) && pathParts.length > 0) {
3475
+ return {
3476
+ bucket: pathParts[0],
3477
+ region: defaults.region || DEFAULT_AWS_REGION,
3478
+ endpoint: `${url.protocol}//${url.host}`,
3479
+ provider: "minio",
3480
+ prefix: pathParts.slice(1).join("/")
3481
+ };
3482
+ }
3483
+ const azureBlob = host.match(AZURE_BLOB_RE);
3484
+ if (azureBlob && pathParts.length > 0) {
3485
+ return {
3486
+ bucket: pathParts[0],
3487
+ region: defaults.region || "",
3488
+ endpoint: `${url.protocol}//${url.host}`,
3489
+ provider: "azure",
3490
+ prefix: pathParts.slice(1).join("/")
3491
+ };
3492
+ }
3493
+ const storjGateway = host.match(STORJ_GATEWAY_RE);
3494
+ if (storjGateway && pathParts.length > 0) {
3495
+ return {
3496
+ bucket: pathParts[0],
3497
+ region: storjGateway[1] || defaults.region || "us1",
3498
+ endpoint: `${url.protocol}//${url.host}`,
3499
+ provider: "storj",
3500
+ prefix: pathParts.slice(1).join("/")
3501
+ };
3502
+ }
3503
+ const storjLink = host.match(STORJ_LINK_RE);
3504
+ if (storjLink && pathParts.length >= 3 && (pathParts[0] === "raw" || pathParts[0] === "s")) {
3505
+ return {
3506
+ bucket: pathParts[2],
3507
+ region: storjLink[1] || defaults.region || "us1",
3508
+ endpoint: `${url.protocol}//${url.host}/${pathParts[0]}/${pathParts[1]}`,
3509
+ provider: "storj",
3510
+ prefix: pathParts.slice(3).join("/")
3511
+ };
3512
+ }
3513
+ if (STAC_API_PATH_RE.test(url.pathname)) {
3514
+ return {
3515
+ ...defaultResult(defaults),
3516
+ endpoint: `${url.protocol}//${url.host}`
3517
+ };
3518
+ }
3519
+ if (pathParts.length > 0) {
3520
+ const endpoint = `${url.protocol}//${url.host}`;
3521
+ return {
3522
+ bucket: pathParts[0],
3523
+ region: defaults.region || DEFAULT_AWS_REGION,
3524
+ endpoint,
3525
+ provider: defaults.provider || "s3",
3526
+ prefix: pathParts.slice(1).join("/")
3527
+ };
3528
+ }
3529
+ return {
3530
+ ...defaultResult(defaults),
3531
+ endpoint: `${url.protocol}//${url.host}`
3532
+ };
3533
+ } catch {
3534
+ }
3535
+ }
3536
+ const cleaned = stripEdgeSlashes2(trimmed);
3537
+ return {
3538
+ bucket: cleaned,
3539
+ region: defaults.region || DEFAULT_AWS_REGION,
3540
+ endpoint: defaults.endpoint || "",
3541
+ provider: defaults.provider || "s3",
3542
+ prefix: ""
3543
+ };
3544
+ }
3545
+ function looksLikeUrl(input) {
3546
+ const lower = input.trim().toLowerCase();
3547
+ if (lower.startsWith("http://") || lower.startsWith("https://")) return true;
3548
+ for (const scheme of Object.keys(SCHEME_MAP)) {
3549
+ if (lower.startsWith(scheme)) return true;
3550
+ }
3551
+ return false;
3552
+ }
3553
+ function describeParseResult(parsed) {
3554
+ const parts = [];
3555
+ if (parsed.bucket) parts.push(`bucket="${parsed.bucket}"`);
3556
+ if (parsed.endpoint) parts.push(`endpoint="${parsed.endpoint}"`);
3557
+ if (parsed.region && parsed.region !== DEFAULT_AWS_REGION)
3558
+ parts.push(`region="${parsed.region}"`);
3559
+ if (parsed.provider !== "s3") parts.push(`provider=${parsed.provider}`);
3560
+ if (parsed.prefix) parts.push(`prefix="${parsed.prefix}"`);
3561
+ return parts.length > 0 ? `Detected: ${parts.join(", ")}` : "";
3562
+ }
3563
+ function classifyUrl(input) {
3564
+ const trimmed = input.trim();
3565
+ const lower = trimmed.toLowerCase();
3566
+ for (const scheme of Object.keys(SCHEME_MAP)) {
3567
+ if (lower.startsWith(scheme)) {
3568
+ return { kind: "scheme", parsed: parseStorageUrl(trimmed) };
3569
+ }
3570
+ }
3571
+ if (lower.startsWith("http://") || lower.startsWith("https://")) {
3572
+ let url;
3573
+ try {
3574
+ url = new URL(trimmed);
3575
+ } catch {
3576
+ return {
3577
+ kind: "remote-file",
3578
+ url: new URL(`https://${trimmed.replace(/^https?:\/\//i, "")}`)
3579
+ };
3580
+ }
3581
+ const parsed = parseStorageUrl(trimmed);
3582
+ if (parsed.bucket && isKnownBucketHost(url.hostname)) {
3583
+ return { kind: "object-storage", parsed };
3584
+ }
3585
+ if (STAC_API_PATH_RE.test(url.pathname)) {
3586
+ return { kind: "stac-api", url };
3587
+ }
3588
+ return { kind: "remote-file", url };
3589
+ }
3590
+ try {
3591
+ return { kind: "remote-file", url: new URL(trimmed) };
3592
+ } catch {
3593
+ return { kind: "remote-file", url: new URL(`https://${trimmed}`) };
3594
+ }
3595
+ }
3596
+
3597
+ // src/host-detection.ts
3598
+ function extractRootPrefix(pathname) {
3599
+ let clean = pathname.replace(/\/[^/]*\.[^/]*$/, "/");
3600
+ clean = clean.replace(/^\//, "");
3601
+ if (!clean || clean === "/") return "";
3602
+ if (!clean.endsWith("/")) clean += "/";
3603
+ return clean;
3604
+ }
3605
+ function buildBucketUrl(provider, endpoint, bucket, region) {
3606
+ return buildProviderBaseUrl(
3607
+ provider === "unknown" ? "s3" : provider,
3608
+ endpoint,
3609
+ bucket,
3610
+ region || ""
3611
+ );
3612
+ }
3613
+ function parsedToHost(parsed, host, rootPrefix) {
3614
+ if (!parsed.bucket || !isKnownBucketHost(host)) return null;
3615
+ const provider = parsed.provider === "unknown" ? "s3" : parsed.provider;
3616
+ return {
3617
+ provider,
3618
+ bucket: parsed.bucket,
3619
+ region: parsed.region,
3620
+ endpoint: parsed.endpoint,
3621
+ rootPrefix,
3622
+ bucketUrl: buildBucketUrl(parsed.provider, parsed.endpoint, parsed.bucket, parsed.region)
3623
+ };
3624
+ }
3625
+ function detectHostBucket() {
3626
+ if (typeof window === "undefined") return null;
3627
+ const url = new URL(window.location.href);
3628
+ const urlParam = url.searchParams.get("url");
3629
+ if (urlParam) {
3630
+ try {
3631
+ const paramUrl = new URL(urlParam);
3632
+ const parsed2 = parseStorageUrl(urlParam);
3633
+ const host = parsedToHost(parsed2, paramUrl.hostname, "");
3634
+ if (host) return host;
3635
+ } catch {
3636
+ }
3637
+ }
3638
+ const parsed = parseStorageUrl(window.location.href);
3639
+ if (!parsed.bucket || !isKnownBucketHost(url.hostname)) return null;
3640
+ const prefixPath = parsed.prefix ? `/${parsed.prefix}` : "";
3641
+ const rootPrefix = extractRootPrefix(prefixPath || "/");
3642
+ const provider = parsed.provider === "unknown" ? "s3" : parsed.provider;
3643
+ return {
3644
+ provider,
3645
+ bucket: parsed.bucket,
3646
+ region: parsed.region,
3647
+ endpoint: parsed.endpoint,
3648
+ rootPrefix,
3649
+ bucketUrl: buildBucketUrl(parsed.provider, parsed.endpoint, parsed.bucket, parsed.region)
3650
+ };
3651
+ }
3652
+ function applyStacItemStorageHints(input, item) {
3653
+ const hints = extractStorageHints(item);
3654
+ const merged = applyStorageHintsToConnection(input, hints);
3655
+ if (import.meta.env?.DEV && (hints.region || hints.endpoint || hints.requesterPays)) {
3656
+ console.debug("[host-detection] applied STAC storage hints", hints);
3657
+ }
3658
+ return merged;
3659
+ }
3660
+
3661
+ // src/local-storage.ts
3662
+ function loadFromStorage(key, defaultValue) {
3663
+ if (typeof window === "undefined") return defaultValue;
3664
+ try {
3665
+ const raw = localStorage.getItem(key);
3666
+ if (raw) return JSON.parse(raw);
3667
+ } catch {
3668
+ }
3669
+ return defaultValue;
3670
+ }
3671
+ function persistToStorage(key, value) {
3672
+ if (typeof window === "undefined") return;
3673
+ try {
3674
+ localStorage.setItem(key, JSON.stringify(value));
3675
+ } catch {
3676
+ }
3677
+ }
3678
+
3679
+ // src/lru.ts
3680
+ var LruCache = class {
3681
+ map = /* @__PURE__ */ new Map();
3682
+ max;
3683
+ onEvict;
3684
+ constructor(opts) {
3685
+ if (opts.max <= 0) throw new Error("LruCache: max must be > 0");
3686
+ this.max = opts.max;
3687
+ this.onEvict = opts.onEvict;
3688
+ }
3689
+ get size() {
3690
+ return this.map.size;
3691
+ }
3692
+ has(key) {
3693
+ return this.map.has(key);
3694
+ }
3695
+ get(key) {
3696
+ const v = this.map.get(key);
3697
+ if (v === void 0) return void 0;
3698
+ this.map.delete(key);
3699
+ this.map.set(key, v);
3700
+ return v;
3701
+ }
3702
+ set(key, value) {
3703
+ if (this.map.has(key)) {
3704
+ this.map.delete(key);
3705
+ }
3706
+ this.map.set(key, value);
3707
+ while (this.map.size > this.max) {
3708
+ const oldest = this.map.keys().next().value;
3709
+ if (oldest === void 0) break;
3710
+ const evicted = this.map.get(oldest);
3711
+ this.map.delete(oldest);
3712
+ this.onEvict?.(oldest, evicted);
3713
+ }
3714
+ }
3715
+ delete(key) {
3716
+ const v = this.map.get(key);
3717
+ if (v === void 0) return false;
3718
+ this.map.delete(key);
3719
+ this.onEvict?.(key, v);
3720
+ return true;
3721
+ }
3722
+ clear() {
3723
+ if (this.onEvict) {
3724
+ for (const [k, v] of this.map) this.onEvict(k, v);
3725
+ }
3726
+ this.map.clear();
3727
+ }
3728
+ };
3729
+
3730
+ // src/map-pixel-inspect.ts
3731
+ function attachPixelInspector(map, { probe, onStart, onResult }) {
3732
+ let active = null;
3733
+ let detached = false;
3734
+ const handler = (event) => {
3735
+ if (detached) return;
3736
+ if (active) active.abort();
3737
+ const ctrl = new AbortController();
3738
+ active = ctrl;
3739
+ onStart();
3740
+ void (async () => {
3741
+ let payload = null;
3742
+ try {
3743
+ payload = await probe({
3744
+ lng: event.lngLat.lng,
3745
+ lat: event.lngLat.lat,
3746
+ signal: ctrl.signal
3747
+ });
3748
+ } catch (err) {
3749
+ if (isHelperAbort(err, ctrl.signal)) return;
3750
+ payload = null;
3751
+ }
3752
+ if (ctrl.signal.aborted && active === ctrl) {
3753
+ return;
3754
+ }
3755
+ if (active === ctrl) active = null;
3756
+ onResult(payload);
3757
+ })();
3758
+ };
3759
+ map.on("click", handler);
3760
+ return function detach() {
3761
+ if (detached) return;
3762
+ detached = true;
3763
+ map.off("click", handler);
3764
+ if (active) {
3765
+ active.abort();
3766
+ active = null;
3767
+ }
3768
+ };
3769
+ }
3770
+ function isHelperAbort(err, signal) {
3771
+ if (!signal.aborted) return false;
3772
+ if (err instanceof DOMException && err.name === "AbortError") return true;
3773
+ if (err && typeof err === "object" && err.name === "AbortError") {
3774
+ return true;
3775
+ }
3776
+ return false;
3777
+ }
3778
+
3779
+ // src/markdown-sql.ts
3780
+ async function parseMarkdownDocument(markdown) {
3781
+ let frontmatter = {};
3782
+ let content = markdown;
3783
+ const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---\n/);
3784
+ if (fmMatch) {
3785
+ try {
3786
+ const { default: YAML } = await import('yaml');
3787
+ frontmatter = YAML.parse(fmMatch[1]) || {};
3788
+ } catch {
3789
+ }
3790
+ content = markdown.slice(fmMatch[0].length);
3791
+ }
3792
+ const sqlBlocks = [];
3793
+ const lines = content.split("\n");
3794
+ let i = 0;
3795
+ while (i < lines.length) {
3796
+ const line = lines[i];
3797
+ const match = line.match(/^```sql\s+(\w[\w-]*)\s*$/);
3798
+ if (match) {
3799
+ const name = match[1];
3800
+ const startLine = i;
3801
+ const sqlLines = [];
3802
+ i++;
3803
+ while (i < lines.length && lines[i] !== "```") {
3804
+ sqlLines.push(lines[i]);
3805
+ i++;
3806
+ }
3807
+ sqlBlocks.push({
3808
+ name,
3809
+ sql: sqlLines.join("\n"),
3810
+ startLine,
3811
+ endLine: i
3812
+ });
3813
+ }
3814
+ i++;
3815
+ }
3816
+ return { frontmatter, content, sqlBlocks };
3817
+ }
3818
+ function interpolateTemplates(text, queryResults) {
3819
+ return text.replace(/\{(\w+)\.rows\[(\d+)\]\.(\w+)\}/g, (match, queryName, rowIdx, colName) => {
3820
+ const rows = queryResults.get(queryName);
3821
+ if (!rows) return match;
3822
+ const row = rows[parseInt(rowIdx, 10)];
3823
+ if (!row) return match;
3824
+ const value = row[colName];
3825
+ return value !== void 0 ? String(value) : match;
3826
+ });
3827
+ }
3828
+ function markSqlBlocks(content) {
3829
+ return content.replace(
3830
+ /```sql[ \t]+(\w[\w-]*)[ \t]*\n([\s\S]*?)```/g,
3831
+ (_, name) => `<div data-sql-block="${name}"></div>`
3832
+ );
3833
+ }
3834
+
3835
+ // src/markdown-sql-context.ts
3836
+ var MarkdownSqlContext = class {
3837
+ engine;
3838
+ connId;
3839
+ prefix;
3840
+ results = /* @__PURE__ */ new Map();
3841
+ constructor(engine, connId, prefix = "") {
3842
+ this.engine = engine;
3843
+ this.connId = connId;
3844
+ this.prefix = prefix;
3845
+ }
3846
+ /** Execute a SQL query and store the result under the given name. */
3847
+ async executeSql(sql, queryName) {
3848
+ const transformedSql = this.transformPaths(sql);
3849
+ const result = await this.engine.query(this.connId, transformedSql);
3850
+ const rows = result.rows ?? [];
3851
+ this.results.set(queryName, { result, rows });
3852
+ return rows;
3853
+ }
3854
+ /**
3855
+ * Transform relative file paths in SQL to full S3 URLs.
3856
+ * e.g. read_parquet('data.parquet') becomes read_parquet('s3://bucket/prefix/data.parquet').
3857
+ */
3858
+ transformPaths(sql) {
3859
+ if (!this.connId || !this.prefix) return sql;
3860
+ return sql.replace(/(read_(?:parquet|csv|json|csv_auto))\('([^']+)'\)/g, (match, fn, path) => {
3861
+ if (path.startsWith("s3://") || path.startsWith("http") || path.startsWith("/")) {
3862
+ return match;
3863
+ }
3864
+ const fullPath = `s3://${this.prefix}/${path}`;
3865
+ return `${fn}('${fullPath}')`;
3866
+ });
3867
+ }
3868
+ getResult(queryName) {
3869
+ return this.results.get(queryName);
3870
+ }
3871
+ getAllResults() {
3872
+ const map = /* @__PURE__ */ new Map();
3873
+ for (const [name, { rows }] of this.results) {
3874
+ map.set(name, rows);
3875
+ }
3876
+ return map;
3877
+ }
3878
+ getColumns(queryName) {
3879
+ const entry = this.results.get(queryName);
3880
+ if (!entry) return [];
3881
+ return entry.result.columns;
3882
+ }
3883
+ };
3884
+
3885
+ // src/notebook.ts
3886
+ var PREFIX = "nb-";
3887
+ function el(tag, classNames = []) {
3888
+ const e = document.createElement(tag);
3889
+ if (classNames.length) e.className = classNames.map((c) => PREFIX + c).join(" ");
3890
+ return e;
3891
+ }
3892
+ function escapeHTML(raw) {
3893
+ return raw.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3894
+ }
3895
+ function joinText(text) {
3896
+ if (Array.isArray(text)) return text.join("");
3897
+ return text ?? "";
3898
+ }
3899
+ var IMAGE_FORMATS = ["image/png", "image/jpeg", "image/gif", "image/webp"];
3900
+ function renderImage(format, data) {
3901
+ const img = el("img", ["image-output"]);
3902
+ img.src = `data:${format};base64,${joinText(data).replace(/\n/g, "")}`;
3903
+ return img;
3904
+ }
3905
+ function renderDisplayData(output, config) {
3906
+ const getData = (mime) => output.data?.[mime] ?? output[mime];
3907
+ for (const fmt of IMAGE_FORMATS) {
3908
+ const d = getData(fmt);
3909
+ if (d) return renderImage(fmt, d);
3910
+ }
3911
+ const svg = getData("image/svg+xml") ?? getData("text/svg+xml");
3912
+ if (svg) {
3913
+ const wrapper = el("div", ["svg-output"]);
3914
+ wrapper.innerHTML = joinText(svg);
3915
+ return wrapper;
3916
+ }
3917
+ const html = getData("text/html");
3918
+ if (html) {
3919
+ const wrapper = el("div", ["html-output"]);
3920
+ wrapper.innerHTML = joinText(html);
3921
+ return wrapper;
3922
+ }
3923
+ const md = getData("text/markdown");
3924
+ if (md) {
3925
+ const wrapper = el("div", ["html-output"]);
3926
+ wrapper.innerHTML = config.markdown(joinText(md));
3927
+ return wrapper;
3928
+ }
3929
+ const latex = getData("text/latex");
3930
+ if (latex) {
3931
+ const wrapper = el("div", ["latex-output"]);
3932
+ wrapper.textContent = joinText(latex);
3933
+ return wrapper;
3934
+ }
3935
+ const plain = getData("text/plain");
3936
+ if (plain) {
3937
+ const pre = el("pre", ["text-output"]);
3938
+ pre.innerHTML = config.ansi(escapeHTML(joinText(plain)));
3939
+ return pre;
3940
+ }
3941
+ return el("div", ["empty-output"]);
3942
+ }
3943
+ function renderStream(output, config) {
3944
+ const streamName = output.stream ?? output.name ?? "stdout";
3945
+ const pre = el("pre", [streamName]);
3946
+ pre.innerHTML = config.ansi(escapeHTML(joinText(output.text ?? "")));
3947
+ return pre;
3948
+ }
3949
+ function renderError(output, config) {
3950
+ const pre = el("pre", ["pyerr"]);
3951
+ const raw = (output.traceback ?? []).join("\n");
3952
+ pre.innerHTML = config.ansi(escapeHTML(raw));
3953
+ return pre;
3954
+ }
3955
+ function coalesceStreams(outputs) {
3956
+ if (!outputs.length) return outputs;
3957
+ const result = [{ ...outputs[0] }];
3958
+ for (let i = 1; i < outputs.length; i++) {
3959
+ const o = outputs[i];
3960
+ const last = result[result.length - 1];
3961
+ if (o.output_type === "stream" && last.output_type === "stream" && (o.stream ?? o.name) === (last.stream ?? last.name)) {
3962
+ const lastText = Array.isArray(last.text) ? last.text : [last.text ?? ""];
3963
+ const oText = Array.isArray(o.text) ? o.text : [o.text ?? ""];
3964
+ last.text = lastText.concat(oText);
3965
+ } else {
3966
+ result.push({ ...o });
3967
+ }
3968
+ }
3969
+ return result;
3970
+ }
3971
+ function renderCodeCell(cell, lang, config) {
3972
+ const cellEl = el("div", ["cell", "code-cell"]);
3973
+ const source = joinText(cell.source ?? cell.input ?? "");
3974
+ if (source) {
3975
+ const holder = el("div", ["input"]);
3976
+ const num = cell.prompt_number ?? cell.execution_count;
3977
+ if (typeof num === "number" && num > -1) {
3978
+ holder.setAttribute("data-prompt-number", String(num));
3979
+ }
3980
+ const pre = el("pre");
3981
+ const code = document.createElement("code");
3982
+ code.setAttribute("data-language", lang);
3983
+ code.className = `lang-${lang}`;
3984
+ code.innerHTML = config.highlighter(escapeHTML(source), lang);
3985
+ pre.appendChild(code);
3986
+ holder.appendChild(pre);
3987
+ cellEl.appendChild(holder);
3988
+ }
3989
+ const rawOutputs = coalesceStreams(cell.outputs ?? []);
3990
+ for (const output of rawOutputs) {
3991
+ const outer = el("div", ["output"]);
3992
+ let inner;
3993
+ switch (output.output_type) {
3994
+ case "display_data":
3995
+ case "execute_result":
3996
+ case "pyout":
3997
+ inner = renderDisplayData(output, config);
3998
+ break;
3999
+ case "stream":
4000
+ inner = renderStream(output, config);
4001
+ break;
4002
+ case "error":
4003
+ case "pyerr":
4004
+ inner = renderError(output, config);
4005
+ break;
4006
+ default:
4007
+ inner = el("div", ["empty-output"]);
4008
+ }
4009
+ outer.appendChild(inner);
4010
+ cellEl.appendChild(outer);
4011
+ }
4012
+ return cellEl;
4013
+ }
4014
+ function renderMarkdownCell(cell, config) {
4015
+ const cellEl = el("div", ["cell", "markdown-cell"]);
4016
+ const source = joinText(cell.source ?? cell.input ?? "");
4017
+ cellEl.innerHTML = config.markdown(source);
4018
+ return cellEl;
4019
+ }
4020
+ function renderHeadingCell(cell) {
4021
+ const level = Math.min(Math.max(cell.level ?? 1, 1), 6);
4022
+ const heading = el(`h${level}`, ["cell", "heading-cell"]);
4023
+ heading.innerHTML = escapeHTML(joinText(cell.source ?? cell.input ?? ""));
4024
+ return heading;
4025
+ }
4026
+ function renderRawCell(cell) {
4027
+ const cellEl = el("div", ["cell", "raw-cell"]);
4028
+ cellEl.innerHTML = escapeHTML(joinText(cell.source ?? cell.input ?? ""));
4029
+ return cellEl;
4030
+ }
4031
+ function renderNotebook(raw, config) {
4032
+ const meta = raw.metadata ?? {};
4033
+ const lang = meta.kernelspec?.language ?? meta.language_info?.name ?? meta.language ?? "python";
4034
+ const kernelName = meta.kernelspec?.display_name ?? meta.language_info?.name ?? "";
4035
+ let allCells = [];
4036
+ if (Array.isArray(raw.cells)) {
4037
+ allCells = raw.cells;
4038
+ } else if (Array.isArray(raw.worksheets)) {
4039
+ for (const ws of raw.worksheets) {
4040
+ if (Array.isArray(ws.cells)) allCells.push(...ws.cells);
4041
+ }
4042
+ }
4043
+ const notebook = el("div", ["notebook"]);
4044
+ for (const cell of allCells) {
4045
+ let rendered;
4046
+ switch (cell.cell_type) {
4047
+ case "code":
4048
+ rendered = renderCodeCell(cell, cell.language ?? lang, config);
4049
+ break;
4050
+ case "markdown":
4051
+ rendered = renderMarkdownCell(cell, config);
4052
+ break;
4053
+ case "heading":
4054
+ rendered = renderHeadingCell(cell);
4055
+ break;
4056
+ case "raw":
4057
+ rendered = renderRawCell(cell);
4058
+ break;
4059
+ default:
4060
+ rendered = renderRawCell(cell);
4061
+ }
4062
+ notebook.appendChild(rendered);
4063
+ }
4064
+ return {
4065
+ element: notebook,
4066
+ meta: { kernelName, language: lang, cellCount: allCells.length }
4067
+ };
4068
+ }
4069
+
4070
+ // src/parquet-metadata.ts
4071
+ function mapParquetType(col) {
4072
+ const lt = col.logical_type;
4073
+ if (lt) {
4074
+ if (lt.type === "GEOMETRY" || lt.type === "GEOGRAPHY") return "GEOMETRY";
4075
+ if (lt.type === "STRING" || lt.type === "UTF8") return "VARCHAR";
4076
+ if (lt.type === "JSON") return "JSON";
4077
+ if (lt.type === "UUID") return "UUID";
4078
+ if (lt.type === "ENUM") return "VARCHAR";
4079
+ if (lt.type === "INT" || lt.type === "INTEGER") {
4080
+ const bits = lt.bitWidth ?? 32;
4081
+ const signed = lt.isSigned !== false;
4082
+ if (bits <= 8) return signed ? "TINYINT" : "UTINYINT";
4083
+ if (bits <= 16) return signed ? "SMALLINT" : "USMALLINT";
4084
+ if (bits <= 32) return signed ? "INTEGER" : "UINTEGER";
4085
+ return signed ? "BIGINT" : "UBIGINT";
4086
+ }
4087
+ if (lt.type === "DECIMAL") return `DECIMAL(${lt.precision ?? 18},${lt.scale ?? 0})`;
4088
+ if (lt.type === "DATE") return "DATE";
4089
+ if (lt.type === "TIME") return "TIME";
4090
+ if (lt.type === "TIMESTAMP") return "TIMESTAMP";
4091
+ if (lt.type === "BSON") return "BLOB";
4092
+ }
4093
+ const ct = col.converted_type;
4094
+ if (ct === "UTF8") return "VARCHAR";
4095
+ if (ct === "JSON") return "JSON";
4096
+ if (ct === "DATE") return "DATE";
4097
+ if (ct === "TIMESTAMP_MILLIS" || ct === "TIMESTAMP_MICROS") return "TIMESTAMP";
4098
+ if (ct === "DECIMAL") return `DECIMAL(${col.precision ?? 18},${col.scale ?? 0})`;
4099
+ if (ct === "INT_8") return "TINYINT";
4100
+ if (ct === "INT_16") return "SMALLINT";
4101
+ if (ct === "INT_32") return "INTEGER";
4102
+ if (ct === "INT_64") return "BIGINT";
4103
+ if (ct === "UINT_8") return "UTINYINT";
4104
+ if (ct === "UINT_16") return "USMALLINT";
4105
+ if (ct === "UINT_32") return "UINTEGER";
4106
+ if (ct === "UINT_64") return "UBIGINT";
4107
+ const pt = col.type;
4108
+ if (pt === "BOOLEAN") return "BOOLEAN";
4109
+ if (pt === "INT32") return "INTEGER";
4110
+ if (pt === "INT64") return "BIGINT";
4111
+ if (pt === "INT96") return "TIMESTAMP";
4112
+ if (pt === "FLOAT") return "FLOAT";
4113
+ if (pt === "DOUBLE") return "DOUBLE";
4114
+ if (pt === "BYTE_ARRAY") return "BLOB";
4115
+ if (pt === "FIXED_LEN_BYTE_ARRAY") return "BLOB";
4116
+ return "VARCHAR";
4117
+ }
4118
+ async function readParquetMetadata(url) {
4119
+ const { parquetMetadataAsync, asyncBufferFromUrl } = await import('hyparquet');
4120
+ const file = await asyncBufferFromUrl({ url });
4121
+ const metadata = await parquetMetadataAsync(file);
4122
+ const rowCount = metadata.row_groups.reduce(
4123
+ (sum, rg) => sum + Number(rg.num_rows),
4124
+ 0
4125
+ );
4126
+ const schema = metadata.schema.slice(1).filter((col) => col.num_children === void 0).map((col) => ({
4127
+ name: col.name,
4128
+ type: mapParquetType(col)
4129
+ }));
4130
+ const topLevelColumns = [];
4131
+ const root = metadata.schema[0];
4132
+ const rootChildren = root?.num_children ?? 0;
4133
+ let cursor = 1;
4134
+ for (let i = 0; i < rootChildren && cursor < metadata.schema.length; i++) {
4135
+ const el2 = metadata.schema[cursor];
4136
+ if (el2?.name) topLevelColumns.push(el2.name);
4137
+ cursor += countSubtree(metadata.schema, cursor);
4138
+ }
4139
+ let geo = null;
4140
+ let legacyGeoParquet = false;
4141
+ const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === "geo");
4142
+ if (geoKv) {
4143
+ try {
4144
+ const geoJson = JSON.parse(geoKv.value ?? "");
4145
+ if (geoJson.schema_version && !geoJson.version) {
4146
+ legacyGeoParquet = true;
4147
+ }
4148
+ geo = {
4149
+ primaryColumn: geoJson.primary_column ?? "geometry",
4150
+ columns: {}
4151
+ };
4152
+ if (geoJson.columns) {
4153
+ for (const [colName, colMeta] of Object.entries(geoJson.columns)) {
4154
+ geo.columns[colName] = {
4155
+ encoding: colMeta.encoding ?? "WKB",
4156
+ geometryTypes: colMeta.geometry_types ?? [],
4157
+ crs: colMeta.crs ?? null,
4158
+ bbox: colMeta.bbox
4159
+ };
4160
+ }
4161
+ }
4162
+ } catch {
4163
+ }
4164
+ }
4165
+ const createdBy = metadata.created_by ?? null;
4166
+ const numRowGroups = metadata.row_groups.length;
4167
+ let compression = null;
4168
+ if (numRowGroups > 0 && metadata.row_groups[0].columns) {
4169
+ const codecs = /* @__PURE__ */ new Set();
4170
+ for (const col of metadata.row_groups[0].columns) {
4171
+ const codec = col.meta_data?.codec;
4172
+ if (codec) codecs.add(codec);
4173
+ }
4174
+ if (codecs.size === 1) {
4175
+ compression = [...codecs][0];
4176
+ } else if (codecs.size > 1) {
4177
+ compression = [...codecs].join(", ");
4178
+ }
4179
+ }
4180
+ return {
4181
+ rowCount,
4182
+ schema,
4183
+ topLevelColumns,
4184
+ geo,
4185
+ legacyGeoParquet,
4186
+ createdBy,
4187
+ numRowGroups,
4188
+ compression
4189
+ };
4190
+ }
4191
+ function countSubtree(schema, start) {
4192
+ const el2 = schema[start];
4193
+ if (!el2) return 0;
4194
+ const n = el2.num_children ?? 0;
4195
+ let cursor = start + 1;
4196
+ for (let i = 0; i < n; i++) {
4197
+ cursor += countSubtree(schema, cursor);
4198
+ }
4199
+ return cursor - start;
4200
+ }
4201
+ function extractEpsgFromGeoMeta(geo) {
4202
+ const primaryCol = geo.columns[geo.primaryColumn];
4203
+ if (!primaryCol?.crs) return null;
4204
+ const crs = primaryCol.crs;
4205
+ if (crs.type === "name" && crs.properties?.name?.includes("CRS84")) return null;
4206
+ if (crs.id?.authority === "EPSG") {
4207
+ const code = crs.id.code;
4208
+ if (WGS84_CODES.has(code)) return null;
4209
+ return `EPSG:${code}`;
4210
+ }
4211
+ return null;
4212
+ }
4213
+ function extractGeometryTypes(geo) {
4214
+ const primaryCol = geo.columns[geo.primaryColumn];
4215
+ if (!primaryCol?.geometryTypes?.length) return [];
4216
+ const typeMap = {
4217
+ Point: "point",
4218
+ LineString: "linestring",
4219
+ Polygon: "polygon",
4220
+ MultiPoint: "multipoint",
4221
+ MultiLineString: "multilinestring",
4222
+ MultiPolygon: "multipolygon"
4223
+ };
4224
+ const types = [];
4225
+ for (const raw of primaryCol.geometryTypes) {
4226
+ const base = raw.split(" ")[0];
4227
+ const mapped = typeMap[base];
4228
+ if (mapped && !types.includes(mapped)) types.push(mapped);
4229
+ }
4230
+ return types;
4231
+ }
4232
+ function extractBounds(geo) {
4233
+ const primaryCol = geo.columns[geo.primaryColumn];
4234
+ if (!primaryCol?.bbox || primaryCol.bbox.length < 4) return null;
4235
+ return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
4236
+ }
4237
+
4238
+ // src/stac-facets.ts
4239
+ var THUMBNAIL_ROLES = ["thumbnail", "overview", "visual"];
4240
+ var THUMBNAIL_KEYS = ["thumbnail", "preview", "overview", "rendered_preview", "visual"];
4241
+ var BROWSER_IMAGE_MIME_RE = /^image\/(png|jpe?g|webp|gif|avif|svg\+xml|svg)\b/i;
4242
+ function isBrowserRenderableAsset(asset) {
4243
+ if (!asset.type) return true;
4244
+ return BROWSER_IMAGE_MIME_RE.test(asset.type);
4245
+ }
4246
+ function extractThumbnailHref(item) {
4247
+ const assets = item.assets ?? {};
4248
+ for (const role of THUMBNAIL_ROLES) {
4249
+ for (const asset of Object.values(assets)) {
4250
+ if (Array.isArray(asset?.roles) && asset.roles.includes(role) && asset.href && isBrowserRenderableAsset(asset)) {
4251
+ return asset.href;
4252
+ }
4253
+ }
4254
+ }
4255
+ for (const key of THUMBNAIL_KEYS) {
4256
+ const asset = assets[key];
4257
+ if (asset?.href && isBrowserRenderableAsset(asset)) return asset.href;
4258
+ }
4259
+ return null;
4260
+ }
4261
+ function collectAssetRoles(item) {
4262
+ const out = /* @__PURE__ */ new Set();
4263
+ for (const asset of Object.values(item.assets ?? {})) {
4264
+ if (Array.isArray(asset?.roles)) {
4265
+ for (const role of asset.roles) {
4266
+ if (typeof role === "string") out.add(role);
4267
+ }
4268
+ }
4269
+ }
4270
+ return [...out].sort();
4271
+ }
4272
+ function readNumber(props, key) {
4273
+ const v = props[key];
4274
+ if (typeof v === "number" && Number.isFinite(v)) return v;
4275
+ if (typeof v === "string") {
4276
+ const n = Number(v);
4277
+ return Number.isFinite(n) ? n : null;
4278
+ }
4279
+ return null;
4280
+ }
4281
+ function readString(props, key) {
4282
+ const v = props[key];
4283
+ return typeof v === "string" && v.length > 0 ? v : null;
4284
+ }
4285
+ function readStringArray(props, key) {
4286
+ const v = props[key];
4287
+ if (!Array.isArray(v)) return [];
4288
+ return v.filter((x) => typeof x === "string");
4289
+ }
4290
+ function extractItemView(item) {
4291
+ const props = item.properties ?? {};
4292
+ const bbox = Array.isArray(item.bbox) && item.bbox.length >= 4 ? [
4293
+ Number(item.bbox[0]),
4294
+ Number(item.bbox[1]),
4295
+ Number(item.bbox[2]),
4296
+ Number(item.bbox[3])
4297
+ ] : null;
4298
+ return {
4299
+ id: String(item.id ?? ""),
4300
+ collection: typeof item.collection === "string" ? item.collection : null,
4301
+ bbox,
4302
+ datetime: readString(props, "datetime") ?? readString(props, "start_datetime"),
4303
+ endDatetime: readString(props, "end_datetime"),
4304
+ cloudCover: readNumber(props, "eo:cloud_cover"),
4305
+ gsd: readNumber(props, "gsd"),
4306
+ platform: readString(props, "platform"),
4307
+ constellation: readString(props, "constellation"),
4308
+ instruments: readStringArray(props, "instruments"),
4309
+ epsg: readNumber(props, "proj:epsg"),
4310
+ thumbnailHref: extractThumbnailHref(item),
4311
+ assetRoles: collectAssetRoles(item),
4312
+ raw: item
4313
+ };
4314
+ }
4315
+ var DATETIME_HISTOGRAM_BINS_MAX = 64;
4316
+ var DATETIME_HISTOGRAM_BINS = 32;
4317
+ var MS_PER_DAY = 864e5;
4318
+ var MS_PER_YEAR = MS_PER_DAY * 365.25;
4319
+ var NUMERIC_FIELDS = ["cloudCover", "gsd"];
4320
+ var ENUM_FIELDS = [
4321
+ "collection",
4322
+ "platform",
4323
+ "constellation",
4324
+ "instruments",
4325
+ "assetRoles"
4326
+ ];
4327
+ function buildNumericFacet(views, field) {
4328
+ let min = Number.POSITIVE_INFINITY;
4329
+ let max = Number.NEGATIVE_INFINITY;
4330
+ let count = 0;
4331
+ for (const v of views) {
4332
+ const value = v[field];
4333
+ if (value == null || !Number.isFinite(value)) continue;
4334
+ if (value < min) min = value;
4335
+ if (value > max) max = value;
4336
+ count++;
4337
+ }
4338
+ if (count === 0 || min === max) return null;
4339
+ return { kind: "numeric", field, min, max, count };
4340
+ }
4341
+ function buildEnumFacet(views, field) {
4342
+ const counts = /* @__PURE__ */ new Map();
4343
+ for (const v of views) {
4344
+ const raw = v[field];
4345
+ if (raw == null) continue;
4346
+ if (Array.isArray(raw)) {
4347
+ for (const item of raw) counts.set(item, (counts.get(item) ?? 0) + 1);
4348
+ } else if (typeof raw === "string" && raw.length > 0) {
4349
+ counts.set(raw, (counts.get(raw) ?? 0) + 1);
4350
+ }
4351
+ }
4352
+ if (counts.size < 2) return null;
4353
+ const values = [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
4354
+ return { kind: "enum", field, values };
4355
+ }
4356
+ function pickGranularity(spanMs) {
4357
+ if (spanMs <= 90 * MS_PER_DAY) return "day";
4358
+ if (spanMs <= 2 * MS_PER_YEAR) return "week";
4359
+ if (spanMs <= 20 * MS_PER_YEAR) return "month";
4360
+ return "year";
4361
+ }
4362
+ function floorUtcDay(t) {
4363
+ const d = new Date(t);
4364
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
4365
+ }
4366
+ function floorIsoWeek(t) {
4367
+ const dayStart = floorUtcDay(t);
4368
+ const dow = new Date(dayStart).getUTCDay();
4369
+ const offsetDays = (dow + 6) % 7;
4370
+ return dayStart - offsetDays * MS_PER_DAY;
4371
+ }
4372
+ function floorUtcMonth(t) {
4373
+ const d = new Date(t);
4374
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1);
4375
+ }
4376
+ function floorUtcYear(t) {
4377
+ const d = new Date(t);
4378
+ return Date.UTC(d.getUTCFullYear(), 0, 1);
4379
+ }
4380
+ function floorBin(t, g) {
4381
+ switch (g) {
4382
+ case "day":
4383
+ return floorUtcDay(t);
4384
+ case "week":
4385
+ return floorIsoWeek(t);
4386
+ case "month":
4387
+ return floorUtcMonth(t);
4388
+ case "year":
4389
+ return floorUtcYear(t);
4390
+ }
4391
+ }
4392
+ function nextBin(t, g) {
4393
+ const d = new Date(t);
4394
+ switch (g) {
4395
+ case "day":
4396
+ return t + MS_PER_DAY;
4397
+ case "week":
4398
+ return t + 7 * MS_PER_DAY;
4399
+ case "month":
4400
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 1);
4401
+ case "year":
4402
+ return Date.UTC(d.getUTCFullYear() + 1, 0, 1);
4403
+ }
4404
+ }
4405
+ function buildDatetimeFacet(views) {
4406
+ const timestamps = [];
4407
+ for (const v of views) {
4408
+ if (!v.datetime) continue;
4409
+ const t = Date.parse(v.datetime);
4410
+ if (Number.isFinite(t)) timestamps.push(t);
4411
+ }
4412
+ if (timestamps.length < 2) return null;
4413
+ let min = timestamps[0];
4414
+ let max = timestamps[0];
4415
+ for (const t of timestamps) {
4416
+ if (t < min) min = t;
4417
+ if (t > max) max = t;
4418
+ }
4419
+ if (min === max) return null;
4420
+ let granularity = pickGranularity(max - min);
4421
+ let binEdges = computeBinEdges(min, max, granularity);
4422
+ while (binEdges.length > DATETIME_HISTOGRAM_BINS_MAX && granularity !== "year") {
4423
+ granularity = coarsenGranularity(granularity);
4424
+ binEdges = computeBinEdges(min, max, granularity);
4425
+ }
4426
+ const bins = new Array(binEdges.length).fill(0);
4427
+ for (const t of timestamps) {
4428
+ const idx = findBinIndex(binEdges, t);
4429
+ bins[idx]++;
4430
+ }
4431
+ return {
4432
+ kind: "datetime",
4433
+ field: "datetime",
4434
+ min: new Date(min).toISOString(),
4435
+ max: new Date(max).toISOString(),
4436
+ count: timestamps.length,
4437
+ bins,
4438
+ granularity,
4439
+ binEdges
4440
+ };
4441
+ }
4442
+ function coarsenGranularity(g) {
4443
+ switch (g) {
4444
+ case "day":
4445
+ return "week";
4446
+ case "week":
4447
+ return "month";
4448
+ case "month":
4449
+ case "year":
4450
+ return "year";
4451
+ }
4452
+ }
4453
+ function computeBinEdges(min, max, g) {
4454
+ const edges = [];
4455
+ let cursor = floorBin(min, g);
4456
+ const stop = max;
4457
+ for (let i = 0; i < DATETIME_HISTOGRAM_BINS_MAX * 4 && cursor <= stop; i++) {
4458
+ edges.push(cursor);
4459
+ cursor = nextBin(cursor, g);
4460
+ }
4461
+ return edges;
4462
+ }
4463
+ function findBinIndex(edges, t) {
4464
+ if (t <= edges[0]) return 0;
4465
+ if (t >= edges[edges.length - 1]) return edges.length - 1;
4466
+ let lo = 0;
4467
+ let hi = edges.length - 1;
4468
+ while (lo < hi) {
4469
+ const mid = lo + hi + 1 >>> 1;
4470
+ if (edges[mid] <= t) lo = mid;
4471
+ else hi = mid - 1;
4472
+ }
4473
+ return lo;
4474
+ }
4475
+ function buildFacets(views) {
4476
+ const numeric = [];
4477
+ for (const field of NUMERIC_FIELDS) {
4478
+ const f = buildNumericFacet(views, field);
4479
+ if (f) numeric.push(f);
4480
+ }
4481
+ const enums = [];
4482
+ for (const field of ENUM_FIELDS) {
4483
+ const f = buildEnumFacet(views, field);
4484
+ if (f) enums.push(f);
4485
+ }
4486
+ return {
4487
+ datetime: buildDatetimeFacet(views),
4488
+ numeric,
4489
+ enums,
4490
+ total: views.length
4491
+ };
4492
+ }
4493
+ function inNumericRange(value, range) {
4494
+ if (value == null || !Number.isFinite(value)) return false;
4495
+ if (range.min != null && value < range.min) return false;
4496
+ if (range.max != null && value > range.max) return false;
4497
+ return true;
4498
+ }
4499
+ function inEnumSet(value, allow) {
4500
+ if (allow.length === 0) return true;
4501
+ if (value == null) return false;
4502
+ if (Array.isArray(value)) return value.some((v) => allow.includes(v));
4503
+ return allow.includes(value);
4504
+ }
4505
+ function inDatetimeRange(value, range) {
4506
+ if (!value) return false;
4507
+ const t = Date.parse(value);
4508
+ if (!Number.isFinite(t)) return false;
4509
+ if (range.min) {
4510
+ const m = Date.parse(range.min);
4511
+ if (Number.isFinite(m) && t < m) return false;
4512
+ }
4513
+ if (range.max) {
4514
+ const m = Date.parse(range.max);
4515
+ if (Number.isFinite(m) && t > m) return false;
4516
+ }
4517
+ return true;
4518
+ }
4519
+ function applyFacets(views, state) {
4520
+ if (!state) return views;
4521
+ const dt = state.datetime;
4522
+ const num = state.numeric;
4523
+ const enums = state.enums;
4524
+ const hasDatetime = dt && (dt.min || dt.max);
4525
+ const hasNumeric = num && Object.values(num).some((r) => r && (r.min != null || r.max != null));
4526
+ const hasEnums = enums && Object.values(enums).some((a) => Array.isArray(a) && a.length > 0);
4527
+ if (!hasDatetime && !hasNumeric && !hasEnums) return views;
4528
+ return views.filter((view) => {
4529
+ if (hasDatetime && dt && !inDatetimeRange(view.datetime, dt)) return false;
4530
+ if (hasNumeric && num) {
4531
+ for (const field of NUMERIC_FIELDS) {
4532
+ const range = num[field];
4533
+ if (!range || range.min == null && range.max == null) continue;
4534
+ if (!inNumericRange(view[field], range)) return false;
4535
+ }
4536
+ }
4537
+ if (hasEnums && enums) {
4538
+ for (const field of ENUM_FIELDS) {
4539
+ const allow = enums[field];
4540
+ if (!Array.isArray(allow) || allow.length === 0) continue;
4541
+ if (!inEnumSet(view[field], allow)) return false;
4542
+ }
4543
+ }
4544
+ return true;
4545
+ });
4546
+ }
4547
+ function sortViews(views, sort) {
4548
+ const arr = views.slice();
4549
+ const cmp = getComparator(sort);
4550
+ arr.sort(cmp);
4551
+ return arr;
4552
+ }
4553
+ function getComparator(sort) {
4554
+ switch (sort) {
4555
+ case "datetime-desc":
4556
+ return (a, b) => compareNullableNumber(parseTime(b.datetime), parseTime(a.datetime));
4557
+ case "datetime-asc":
4558
+ return (a, b) => compareNullableNumber(parseTime(a.datetime), parseTime(b.datetime));
4559
+ case "cloud-asc":
4560
+ return (a, b) => compareNullableNumber(a.cloudCover, b.cloudCover);
4561
+ case "cloud-desc":
4562
+ return (a, b) => compareNullableNumber(b.cloudCover, a.cloudCover);
4563
+ case "gsd-asc":
4564
+ return (a, b) => compareNullableNumber(a.gsd, b.gsd);
4565
+ case "gsd-desc":
4566
+ return (a, b) => compareNullableNumber(b.gsd, a.gsd);
4567
+ case "id-asc":
4568
+ return (a, b) => a.id.localeCompare(b.id);
4569
+ }
4570
+ }
4571
+ function parseTime(s) {
4572
+ if (!s) return null;
4573
+ const t = Date.parse(s);
4574
+ return Number.isFinite(t) ? t : null;
4575
+ }
4576
+ function compareNullableNumber(a, b) {
4577
+ const aMissing = a == null || !Number.isFinite(a);
4578
+ const bMissing = b == null || !Number.isFinite(b);
4579
+ if (aMissing && bMissing) return 0;
4580
+ if (aMissing) return 1;
4581
+ if (bMissing) return -1;
4582
+ return a - b;
4583
+ }
4584
+ function hasActiveFilters(state) {
4585
+ if (!state) return false;
4586
+ if (state.datetime && (state.datetime.min || state.datetime.max)) return true;
4587
+ if (state.numeric) {
4588
+ for (const r of Object.values(state.numeric)) {
4589
+ if (r && (r.min != null || r.max != null)) return true;
4590
+ }
4591
+ }
4592
+ if (state.enums) {
4593
+ for (const a of Object.values(state.enums)) {
4594
+ if (Array.isArray(a) && a.length > 0) return true;
4595
+ }
4596
+ }
4597
+ return false;
4598
+ }
4599
+ function emptyFacetState() {
4600
+ return {};
4601
+ }
4602
+
4603
+ // src/stac-geoparquet.ts
4604
+ function plainifyArrowValue(value) {
4605
+ if (value === null || value === void 0) return value;
4606
+ const t = typeof value;
4607
+ if (t === "string" || t === "number" || t === "boolean" || t === "bigint") return value;
4608
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
4609
+ return value;
4610
+ }
4611
+ if (Array.isArray(value)) return value.map(plainifyArrowValue);
4612
+ const obj = value;
4613
+ if (typeof obj.toJSON === "function") {
4614
+ return plainifyArrowValue(obj.toJSON());
4615
+ }
4616
+ if (typeof obj.toArray === "function") {
4617
+ return obj.toArray().map(plainifyArrowValue);
4618
+ }
4619
+ if (t === "object") {
4620
+ const out = {};
4621
+ for (const [k, v] of Object.entries(value)) {
4622
+ out[k] = plainifyArrowValue(v);
4623
+ }
4624
+ return out;
4625
+ }
4626
+ return value;
4627
+ }
4628
+ var STAC_GEOPARQUET_REQUIRED_COLUMNS = [
4629
+ "stac_version",
4630
+ "type",
4631
+ "geometry",
4632
+ "assets"
4633
+ ];
4634
+ function isStacGeoparquetSchema(schema) {
4635
+ if (!Array.isArray(schema) || schema.length === 0) return false;
4636
+ const names = new Set(schema.map((c) => c.name));
4637
+ return STAC_GEOPARQUET_REQUIRED_COLUMNS.every((c) => names.has(c));
4638
+ }
4639
+ function flattenStacBbox(bbox) {
4640
+ if (!bbox) return null;
4641
+ if (Array.isArray(bbox)) {
4642
+ if (bbox.length < 4) return null;
4643
+ const [minX, minY, maxX, maxY] = bbox;
4644
+ if (![minX, minY, maxX, maxY].every((v) => Number.isFinite(v))) return null;
4645
+ return [minX, minY, maxX, maxY];
4646
+ }
4647
+ if (typeof bbox === "object") {
4648
+ const { xmin, ymin, xmax, ymax } = bbox;
4649
+ if (![xmin, ymin, xmax, ymax].every((v) => Number.isFinite(v))) return null;
4650
+ return [xmin, ymin, xmax, ymax];
4651
+ }
4652
+ return null;
4653
+ }
4654
+ function resolveStacAssetHref(href, baseUrl) {
4655
+ if (!href) return href;
4656
+ if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(href)) return href;
4657
+ try {
4658
+ return new URL(href, baseUrl).toString();
4659
+ } catch {
4660
+ return href;
4661
+ }
4662
+ }
4663
+ function pickStacPrimaryAsset(assets, preferredKeys) {
4664
+ if (!assets || typeof assets !== "object") return null;
4665
+ const entries = Object.entries(assets).filter(
4666
+ ([, a]) => a && typeof a === "object" && typeof a.href === "string"
4667
+ );
4668
+ if (entries.length === 0) return null;
4669
+ if (preferredKeys) {
4670
+ for (const key of preferredKeys) {
4671
+ const match = entries.find(([k]) => k === key);
4672
+ if (match) return { key: match[0], asset: match[1] };
4673
+ }
4674
+ }
4675
+ const data = entries.find(([k]) => k === "data");
4676
+ if (data) return { key: data[0], asset: data[1] };
4677
+ const byRole = entries.find(([, a]) => Array.isArray(a.roles) && a.roles.includes("data"));
4678
+ if (byRole) return { key: byRole[0], asset: byRole[1] };
4679
+ return { key: entries[0][0], asset: entries[0][1] };
4680
+ }
4681
+ function normalizeAssetsField(value, baseUrl) {
4682
+ if (!value || typeof value !== "object") return void 0;
4683
+ const out = {};
4684
+ for (const [key, raw] of Object.entries(value)) {
4685
+ if (!raw || typeof raw !== "object") continue;
4686
+ const asset = raw;
4687
+ if (typeof asset.href !== "string" || !asset.href) continue;
4688
+ out[key] = {
4689
+ ...asset,
4690
+ href: resolveStacAssetHref(asset.href, baseUrl)
4691
+ };
4692
+ }
4693
+ return Object.keys(out).length > 0 ? out : void 0;
4694
+ }
4695
+ function coerceDatetimeToIso(value) {
4696
+ if (value == null) return void 0;
4697
+ if (value instanceof Date) {
4698
+ const ms = value.getTime();
4699
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
4700
+ }
4701
+ if (typeof value === "bigint") {
4702
+ const ms = Number(value / 1000n);
4703
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
4704
+ }
4705
+ if (typeof value === "number" && Number.isFinite(value)) {
4706
+ const ms = Math.abs(value) > 1e14 ? Math.floor(value / 1e3) : value;
4707
+ return new Date(ms).toISOString();
4708
+ }
4709
+ if (typeof value === "string") {
4710
+ const t = Date.parse(value);
4711
+ return Number.isFinite(t) ? new Date(t).toISOString() : void 0;
4712
+ }
4713
+ return void 0;
4714
+ }
4715
+ function normalizeLinksField(value, baseUrl) {
4716
+ if (!Array.isArray(value)) return void 0;
4717
+ const links = [];
4718
+ for (const raw of value) {
4719
+ if (!raw || typeof raw !== "object") continue;
4720
+ const link = raw;
4721
+ if (typeof link.href !== "string" || typeof link.rel !== "string") continue;
4722
+ links.push({ ...link, href: resolveStacAssetHref(link.href, baseUrl) });
4723
+ }
4724
+ return links.length > 0 ? links : void 0;
4725
+ }
4726
+ function stacRowToItem(row, baseUrl, opts = {}) {
4727
+ const { wkbParser, wkbColumn = "geom_wkb", geometryColumn = "geometry" } = opts;
4728
+ for (const key of ["bbox", "assets", "links", "bands", "stac_extensions"]) {
4729
+ if (row[key] !== void 0 && row[key] !== null) {
4730
+ row[key] = plainifyArrowValue(row[key]);
4731
+ }
4732
+ }
4733
+ let geometry = row[geometryColumn];
4734
+ if (!geometry) {
4735
+ const wkb = row[wkbColumn];
4736
+ if (wkb && wkbParser) {
4737
+ const bytes = wkb instanceof Uint8Array ? wkb : toUint8Array(wkb);
4738
+ if (bytes) {
4739
+ try {
4740
+ geometry = wkbParser(bytes) ?? void 0;
4741
+ } catch {
4742
+ geometry = void 0;
4743
+ }
4744
+ }
4745
+ }
4746
+ }
4747
+ const bbox = flattenStacBbox(row.bbox) ?? void 0;
4748
+ const assets = normalizeAssetsField(row.assets, baseUrl);
4749
+ const links = normalizeLinksField(row.links, baseUrl);
4750
+ const properties = {};
4751
+ const SCALAR_PROP_KEYS = /* @__PURE__ */ new Set([
4752
+ "gsd",
4753
+ "platform",
4754
+ "constellation",
4755
+ "instruments",
4756
+ "mission",
4757
+ "license"
4758
+ ]);
4759
+ const TIMESTAMP_PROP_KEYS = /* @__PURE__ */ new Set([
4760
+ "datetime",
4761
+ "start_datetime",
4762
+ "end_datetime",
4763
+ "created",
4764
+ "updated"
4765
+ ]);
4766
+ for (const [key, value] of Object.entries(row)) {
4767
+ if (value === null || value === void 0) continue;
4768
+ if (key.startsWith("proj:") || key.startsWith("raster:") || key.startsWith("eo:")) {
4769
+ properties[key] = value;
4770
+ continue;
4771
+ }
4772
+ if (TIMESTAMP_PROP_KEYS.has(key)) {
4773
+ const iso = coerceDatetimeToIso(value);
4774
+ if (iso) properties[key] = iso;
4775
+ continue;
4776
+ }
4777
+ if (SCALAR_PROP_KEYS.has(key)) {
4778
+ properties[key] = value;
4779
+ continue;
4780
+ }
4781
+ if (key === "bands") {
4782
+ properties.bands = value;
4783
+ }
4784
+ }
4785
+ const item = {
4786
+ type: "Feature",
4787
+ stac_version: typeof row.stac_version === "string" ? row.stac_version : "1.0.0",
4788
+ id: typeof row.id === "string" ? row.id : String(row.id ?? ""),
4789
+ properties
4790
+ };
4791
+ if (typeof row.collection === "string") item.collection = row.collection;
4792
+ if (Array.isArray(row.stac_extensions)) {
4793
+ item.stac_extensions = row.stac_extensions;
4794
+ }
4795
+ if (bbox) item.bbox = bbox;
4796
+ if (geometry) item.geometry = geometry;
4797
+ if (assets) item.assets = assets;
4798
+ if (links) item.links = links;
4799
+ return item;
4800
+ }
4801
+ function toUint8Array(value) {
4802
+ if (value instanceof Uint8Array) return value;
4803
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
4804
+ if (ArrayBuffer.isView(value)) {
4805
+ const view = value;
4806
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
4807
+ }
4808
+ if (Array.isArray(value)) return new Uint8Array(value);
4809
+ return null;
4810
+ }
4811
+
4812
+ // src/stac-hydrate.ts
4813
+ async function hydrateStacItems(root, baseHref, adapter, opts) {
4814
+ const limit = opts.limit ?? 2e3;
4815
+ const concurrency = opts.concurrency ?? 12;
4816
+ const followPagination = opts.followPagination ?? true;
4817
+ const signal = opts.signal;
4818
+ const urlToKey = opts.urlToKey;
4819
+ const items = [];
4820
+ let truncated = false;
4821
+ const emit = (batch) => {
4822
+ if (batch.length === 0) return;
4823
+ items.push(...batch);
4824
+ opts.onBatch?.(batch);
4825
+ opts.onProgress?.(items.length, void 0);
4826
+ };
4827
+ if (root.kind === "item") {
4828
+ emit([absolutizeItemAssets(root.item, baseHref)]);
4829
+ return { items, truncated: false, rootBaseHref: baseHref };
4830
+ }
4831
+ if (root.kind === "item-collection") {
4832
+ await consumeFeatureCollection(root.fc, baseHref, adapter, {
4833
+ signal,
4834
+ concurrency,
4835
+ limit,
4836
+ followPagination,
4837
+ urlToKey,
4838
+ itemsQuery: opts.itemsQuery,
4839
+ onAccept: (batch) => emit(batch),
4840
+ stopCheck: () => items.length >= limit,
4841
+ onTruncate: () => {
4842
+ truncated = true;
4843
+ }
4844
+ });
4845
+ return { items: items.slice(0, limit), truncated, rootBaseHref: baseHref };
4846
+ }
4847
+ if (root.kind === "collection" || root.kind === "catalog") {
4848
+ const itemLinks = collectItemLinks(root.payload, baseHref);
4849
+ const childLinks = root.kind === "catalog" || itemLinks.length === 0 ? collectChildLinks(root.payload, baseHref) : [];
4850
+ const itemsEndpoint = itemLinks.length === 0 ? collectItemsEndpoint(root.payload, baseHref, opts.itemsQuery) : null;
4851
+ if (itemLinks.length > 0) {
4852
+ await fetchItems(itemLinks, adapter, baseHref, {
4853
+ signal,
4854
+ concurrency,
4855
+ urlToKey,
4856
+ onBatch: (batch) => emit(batch),
4857
+ stopCheck: () => items.length >= limit,
4858
+ onTruncate: () => {
4859
+ truncated = true;
4860
+ }
4861
+ });
4862
+ } else if (itemsEndpoint) {
4863
+ try {
4864
+ const json = await fetchJson(adapter, itemsEndpoint, baseHref, signal, urlToKey);
4865
+ if (isStacFeatureCollection(json)) {
4866
+ await consumeFeatureCollection(json, itemsEndpoint, adapter, {
4867
+ signal,
4868
+ concurrency,
4869
+ limit,
4870
+ followPagination,
4871
+ urlToKey,
4872
+ itemsQuery: opts.itemsQuery,
4873
+ onAccept: (batch) => emit(batch),
4874
+ stopCheck: () => items.length >= limit,
4875
+ onTruncate: () => {
4876
+ truncated = true;
4877
+ }
4878
+ });
4879
+ }
4880
+ } catch {
2995
4881
  }
2996
- const azureBlob = host.match(AZURE_BLOB_RE);
2997
- if (azureBlob && pathParts.length > 0) {
2998
- return {
2999
- bucket: pathParts[0],
3000
- region: defaults.region || "",
3001
- endpoint: `${url.protocol}//${url.host}`,
3002
- provider: "azure",
3003
- prefix: pathParts.slice(1).join("/")
3004
- };
4882
+ }
4883
+ if (!truncated && items.length < limit && childLinks.length > 0) {
4884
+ const queue = [...childLinks];
4885
+ const stopCheck = () => items.length >= limit || signal.aborted;
4886
+ const workerCount = Math.min(concurrency, queue.length);
4887
+ const workers = [];
4888
+ for (let i = 0; i < workerCount; i++) {
4889
+ workers.push(
4890
+ (async () => {
4891
+ while (queue.length > 0) {
4892
+ if (stopCheck()) {
4893
+ if (items.length >= limit) truncated = true;
4894
+ queue.length = 0;
4895
+ return;
4896
+ }
4897
+ const childHref = queue.shift();
4898
+ if (!childHref) return;
4899
+ try {
4900
+ const childJson = await fetchJson(adapter, childHref, baseHref, signal, urlToKey);
4901
+ if (stopCheck()) {
4902
+ if (items.length >= limit) truncated = true;
4903
+ queue.length = 0;
4904
+ return;
4905
+ }
4906
+ const childKind = classifyFetchedJson(childJson);
4907
+ if (childKind.kind === "none") continue;
4908
+ const sub = await hydrateStacItems(childKind, childHref, adapter, {
4909
+ ...opts,
4910
+ limit: limit - items.length,
4911
+ onBatch: (batch) => emit(batch),
4912
+ onProgress: void 0
4913
+ });
4914
+ if (sub.truncated) truncated = true;
4915
+ } catch {
4916
+ }
4917
+ }
4918
+ })()
4919
+ );
3005
4920
  }
3006
- const storjGateway = host.match(STORJ_GATEWAY_RE);
3007
- if (storjGateway && pathParts.length > 0) {
3008
- return {
3009
- bucket: pathParts[0],
3010
- region: storjGateway[1] || defaults.region || "us1",
3011
- endpoint: `${url.protocol}//${url.host}`,
3012
- provider: "storj",
3013
- prefix: pathParts.slice(1).join("/")
3014
- };
4921
+ await Promise.all(workers);
4922
+ }
4923
+ return { items: items.slice(0, limit), truncated, rootBaseHref: baseHref };
4924
+ }
4925
+ return { items, truncated: false, rootBaseHref: baseHref };
4926
+ }
4927
+ function collectItemLinks(payload, baseHref) {
4928
+ return (payload.links ?? []).filter((l) => !!l && typeof l.href === "string" && l.rel === "item").map((l) => absolutizeHref(l.href, baseHref));
4929
+ }
4930
+ function collectChildLinks(payload, baseHref) {
4931
+ return (payload.links ?? []).filter((l) => !!l && typeof l.href === "string" && l.rel === "child").map((l) => absolutizeHref(l.href, baseHref));
4932
+ }
4933
+ function hasStacItemsEndpoint(payload) {
4934
+ return (payload.links ?? []).some((l) => !!l && typeof l.href === "string" && l.rel === "items");
4935
+ }
4936
+ function collectItemsEndpoint(payload, baseHref, query) {
4937
+ const link = (payload.links ?? []).find(
4938
+ (l) => !!l && typeof l.href === "string" && l.rel === "items"
4939
+ );
4940
+ if (!link) return null;
4941
+ return applyItemsQuery(absolutizeHref(link.href, baseHref), query);
4942
+ }
4943
+ function applyItemsQuery(absolute, query) {
4944
+ if (!query) return absolute;
4945
+ try {
4946
+ const url = new URL(absolute);
4947
+ if (query.bbox && query.bbox.length === 4 && !url.searchParams.has("bbox")) {
4948
+ url.searchParams.set("bbox", query.bbox.join(","));
4949
+ }
4950
+ if (query.datetime && !url.searchParams.has("datetime")) {
4951
+ url.searchParams.set("datetime", query.datetime);
4952
+ }
4953
+ if (typeof query.limit === "number" && query.limit > 0 && !url.searchParams.has("limit")) {
4954
+ url.searchParams.set("limit", String(Math.floor(query.limit)));
4955
+ }
4956
+ if (query.filter !== void 0 && query.filter !== null && !url.searchParams.has("filter")) {
4957
+ try {
4958
+ url.searchParams.set("filter", JSON.stringify(query.filter));
4959
+ if (!url.searchParams.has("filter-lang")) {
4960
+ url.searchParams.set("filter-lang", "cql2-json");
4961
+ }
4962
+ } catch {
3015
4963
  }
3016
- const storjLink = host.match(STORJ_LINK_RE);
3017
- if (storjLink && pathParts.length >= 3 && (pathParts[0] === "raw" || pathParts[0] === "s")) {
3018
- return {
3019
- bucket: pathParts[2],
3020
- region: storjLink[1] || defaults.region || "us1",
3021
- endpoint: `${url.protocol}//${url.host}/${pathParts[0]}/${pathParts[1]}`,
3022
- provider: "storj",
3023
- prefix: pathParts.slice(3).join("/")
3024
- };
4964
+ }
4965
+ return url.toString();
4966
+ } catch {
4967
+ return absolute;
4968
+ }
4969
+ }
4970
+ async function consumeFeatureCollection(fc, baseHref, adapter, ctx) {
4971
+ const accepted = fc.features.filter(isStacItem).map((item) => absolutizeItemAssets(item, baseHref));
4972
+ ctx.onAccept(accepted);
4973
+ if (ctx.stopCheck()) {
4974
+ ctx.onTruncate();
4975
+ return;
4976
+ }
4977
+ if (!ctx.followPagination) return;
4978
+ const next = (fc.links ?? []).find((l) => l && l.rel === "next" && typeof l.href === "string");
4979
+ if (!next) return;
4980
+ const nextHref = applyItemsQuery(absolutizeHref(next.href, baseHref), ctx.itemsQuery);
4981
+ if (ctx.signal.aborted) return;
4982
+ try {
4983
+ const json = await fetchJson(adapter, nextHref, baseHref, ctx.signal, ctx.urlToKey);
4984
+ if (!isStacFeatureCollection(json)) return;
4985
+ await consumeFeatureCollection(json, nextHref, adapter, ctx);
4986
+ } catch {
4987
+ }
4988
+ }
4989
+ async function fetchItems(hrefs, adapter, baseHref, ctx) {
4990
+ const queue = [...hrefs];
4991
+ const workers = [];
4992
+ const workerCount = Math.min(ctx.concurrency, queue.length);
4993
+ for (let i = 0; i < workerCount; i++) {
4994
+ workers.push(
4995
+ (async () => {
4996
+ while (queue.length > 0) {
4997
+ if (ctx.signal.aborted) return;
4998
+ if (ctx.stopCheck()) {
4999
+ ctx.onTruncate();
5000
+ queue.length = 0;
5001
+ return;
5002
+ }
5003
+ const href = queue.shift();
5004
+ if (!href) return;
5005
+ try {
5006
+ const json = await fetchJson(adapter, href, baseHref, ctx.signal, ctx.urlToKey);
5007
+ if (isStacItem(json)) ctx.onBatch([absolutizeItemAssets(json, href)]);
5008
+ } catch {
5009
+ }
5010
+ }
5011
+ })()
5012
+ );
5013
+ }
5014
+ await Promise.all(workers);
5015
+ }
5016
+ async function fetchJson(adapter, href, _baseHref, signal, urlToKey) {
5017
+ if (/^https?:/i.test(href)) {
5018
+ const ownKey = urlToKey ? urlToKey(href) : null;
5019
+ if (ownKey !== null) {
5020
+ const buf2 = await adapter.read(ownKey, void 0, void 0, signal);
5021
+ return JSON.parse(new TextDecoder().decode(buf2));
5022
+ }
5023
+ const res = await fetch(href, { signal });
5024
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${href}`);
5025
+ return await res.json();
5026
+ }
5027
+ const buf = await adapter.read(href, void 0, void 0, signal);
5028
+ return JSON.parse(new TextDecoder().decode(buf));
5029
+ }
5030
+ function classifyFetchedJson(json) {
5031
+ if (isStacItem(json)) return { kind: "item", item: json };
5032
+ if (isStacFeatureCollection(json)) return { kind: "item-collection", fc: json };
5033
+ if (isStacCollection(json)) return { kind: "collection", payload: json };
5034
+ if (isStacCatalog(json)) return { kind: "catalog", payload: json };
5035
+ return { kind: "none" };
5036
+ }
5037
+ function absolutizeHref(href, baseHref) {
5038
+ if (/^https?:/i.test(href) || /^s3:/i.test(href) || /^azure:/i.test(href)) return href;
5039
+ try {
5040
+ return new URL(href, baseHref).toString();
5041
+ } catch {
5042
+ return href;
5043
+ }
5044
+ }
5045
+ function absolutizeItemAssets(item, itemHref) {
5046
+ const assets = item.assets;
5047
+ if (!assets) return item;
5048
+ let changed = false;
5049
+ const resolved = {};
5050
+ for (const [key, asset] of Object.entries(assets)) {
5051
+ if (asset?.href && typeof asset.href === "string") {
5052
+ const abs = absolutizeHref(asset.href, itemHref);
5053
+ if (abs !== asset.href) changed = true;
5054
+ resolved[key] = { ...asset, href: abs };
5055
+ } else {
5056
+ resolved[key] = asset;
5057
+ }
5058
+ }
5059
+ return changed ? { ...item, assets: resolved } : item;
5060
+ }
5061
+
5062
+ // src/stac-pushdown.ts
5063
+ var CONFORMANCE_SIGNATURES = [
5064
+ {
5065
+ key: "bbox",
5066
+ matchers: [/ogc-api[/-]features.*\/conf\/core/i, /stac-api.*\/item-search/i]
5067
+ },
5068
+ {
5069
+ key: "datetime",
5070
+ matchers: [/ogc-api[/-]features.*\/conf\/core/i, /stac-api.*\/item-search/i]
5071
+ },
5072
+ {
5073
+ key: "collections",
5074
+ matchers: [/stac-api.*\/item-search/i]
5075
+ },
5076
+ {
5077
+ key: "cql2",
5078
+ matchers: [/stac-api.*\/item-search.*\/filter/i, /ogc-api[/-]features.*\/cql2/i, /cql2-json/i]
5079
+ },
5080
+ {
5081
+ key: "queryables",
5082
+ matchers: [/stac-api.*\/item-search.*\/queryables/i, /ogc-api[/-]features.*\/queryables/i]
5083
+ }
5084
+ ];
5085
+ function sniffApiCapabilities(conformsTo) {
5086
+ const caps = {
5087
+ bbox: false,
5088
+ datetime: false,
5089
+ collections: false,
5090
+ cql2: false,
5091
+ queryables: false
5092
+ };
5093
+ if (!Array.isArray(conformsTo)) return caps;
5094
+ const normalised = conformsTo.filter((s) => typeof s === "string");
5095
+ for (const sig of CONFORMANCE_SIGNATURES) {
5096
+ if (normalised.some((uri) => sig.matchers.some((re) => re.test(uri)))) {
5097
+ caps[sig.key] = true;
5098
+ }
5099
+ }
5100
+ return caps;
5101
+ }
5102
+ function toNativeQuery(state, caps, opts = {}) {
5103
+ const out = {};
5104
+ if (opts.bbox && caps.bbox) out.bbox = opts.bbox;
5105
+ if (typeof opts.limit === "number" && opts.limit > 0) out.limit = Math.floor(opts.limit);
5106
+ if (!state) return out;
5107
+ if (caps.datetime && state.datetime && (state.datetime.min || state.datetime.max)) {
5108
+ out.datetime = formatDatetimeInterval(state.datetime.min, state.datetime.max);
5109
+ }
5110
+ if (caps.collections && state.enums?.collection?.length) {
5111
+ out.collections = [...state.enums.collection];
5112
+ }
5113
+ if (caps.cql2) {
5114
+ const filter = toCql2Filter(state, caps);
5115
+ if (filter) {
5116
+ out.filter = filter;
5117
+ out["filter-lang"] = "cql2-json";
5118
+ }
5119
+ }
5120
+ return out;
5121
+ }
5122
+ function formatDatetimeInterval(min, max) {
5123
+ const lo = min ?? "..";
5124
+ const hi = max ?? "..";
5125
+ if (lo === ".." && hi === "..") return "..";
5126
+ if (lo === hi) return lo;
5127
+ return `${lo}/${hi}`;
5128
+ }
5129
+ function toCql2Filter(state, caps) {
5130
+ if (!state) return null;
5131
+ const clauses = [];
5132
+ if (state.numeric) {
5133
+ const n = state.numeric;
5134
+ pushBetween(clauses, "eo:cloud_cover", n.cloudCover);
5135
+ pushBetween(clauses, "gsd", n.gsd);
5136
+ }
5137
+ if (state.enums) {
5138
+ const e = state.enums;
5139
+ pushIn(clauses, "platform", e.platform);
5140
+ pushIn(clauses, "constellation", e.constellation);
5141
+ pushOverlap(clauses, "instruments", e.instruments);
5142
+ if (!caps.collections) pushIn(clauses, "collection", e.collection);
5143
+ }
5144
+ if (!caps.datetime && state.datetime && (state.datetime.min || state.datetime.max)) {
5145
+ const lo = state.datetime.min;
5146
+ const hi = state.datetime.max;
5147
+ if (lo && hi) {
5148
+ clauses.push({
5149
+ op: "t_intersects",
5150
+ args: [{ property: "datetime" }, { interval: [lo, hi] }]
5151
+ });
5152
+ } else if (lo) {
5153
+ clauses.push({ op: ">=", args: [{ property: "datetime" }, { timestamp: lo }] });
5154
+ } else if (hi) {
5155
+ clauses.push({ op: "<=", args: [{ property: "datetime" }, { timestamp: hi }] });
5156
+ }
5157
+ }
5158
+ if (clauses.length === 0) return null;
5159
+ if (clauses.length === 1) return clauses[0];
5160
+ return { op: "and", args: clauses };
5161
+ }
5162
+ function pushBetween(out, property, range) {
5163
+ if (!range) return;
5164
+ const { min, max } = range;
5165
+ if (min == null && max == null) return;
5166
+ if (min != null && max != null) {
5167
+ out.push({ op: "between", args: [{ property }, [min, max]] });
5168
+ return;
5169
+ }
5170
+ if (min != null) out.push({ op: ">=", args: [{ property }, min] });
5171
+ if (max != null) out.push({ op: "<=", args: [{ property }, max] });
5172
+ }
5173
+ function pushIn(out, property, values) {
5174
+ if (!values || values.length === 0) return;
5175
+ if (values.length === 1) {
5176
+ out.push({ op: "=", args: [{ property }, values[0]] });
5177
+ return;
5178
+ }
5179
+ out.push({ op: "in", args: [{ property }, values] });
5180
+ }
5181
+ function pushOverlap(out, property, values) {
5182
+ if (!values || values.length === 0) return;
5183
+ out.push({ op: "a_overlaps", args: [{ property }, values] });
5184
+ }
5185
+ function residualState(state, caps) {
5186
+ if (!state) return {};
5187
+ const out = {};
5188
+ if (state.datetime && !caps.datetime && !caps.cql2) out.datetime = state.datetime;
5189
+ else if (state.datetime && (caps.datetime || caps.cql2)) ;
5190
+ if (state.numeric) {
5191
+ const remaining = {};
5192
+ for (const [field, range] of Object.entries(state.numeric)) {
5193
+ if (!range) continue;
5194
+ if (caps.cql2) continue;
5195
+ remaining[field] = range;
5196
+ }
5197
+ if (Object.keys(remaining).length > 0) out.numeric = remaining;
5198
+ }
5199
+ if (state.enums) {
5200
+ const remaining = {};
5201
+ for (const [field, values] of Object.entries(state.enums)) {
5202
+ if (!Array.isArray(values) || values.length === 0) continue;
5203
+ if (field === "collection" && caps.collections) continue;
5204
+ if (caps.cql2 && (field === "platform" || field === "constellation" || field === "instruments")) {
5205
+ continue;
3025
5206
  }
3026
- if (STAC_API_PATH_RE.test(url.pathname)) {
3027
- return {
3028
- ...defaultResult(defaults),
3029
- endpoint: `${url.protocol}//${url.host}`
5207
+ remaining[field] = values;
5208
+ }
5209
+ if (Object.keys(remaining).length > 0) out.enums = remaining;
5210
+ }
5211
+ return out;
5212
+ }
5213
+
5214
+ // src/stac-source.ts
5215
+ function emptyPushdown() {
5216
+ return {
5217
+ bbox: false,
5218
+ datetime: false,
5219
+ collection: false,
5220
+ cloudCover: false,
5221
+ gsd: false,
5222
+ epsg: false,
5223
+ platform: false,
5224
+ constellation: false,
5225
+ instruments: false,
5226
+ assetRoles: false
5227
+ };
5228
+ }
5229
+
5230
+ // src/stac-source-api.ts
5231
+ function subtractState(full, residual) {
5232
+ if (!full) return {};
5233
+ const out = {};
5234
+ if (full.datetime && !residual.datetime) out.datetime = full.datetime;
5235
+ if (full.numeric) {
5236
+ const kept = {};
5237
+ const residualNumeric = residual.numeric ?? {};
5238
+ for (const [k, v] of Object.entries(full.numeric)) {
5239
+ if (!v) continue;
5240
+ if (residualNumeric[k]) continue;
5241
+ kept[k] = v;
5242
+ }
5243
+ if (Object.keys(kept).length > 0) out.numeric = kept;
5244
+ }
5245
+ if (full.enums) {
5246
+ const kept = {};
5247
+ const residualEnums = residual.enums ?? {};
5248
+ for (const [k, v] of Object.entries(full.enums)) {
5249
+ if (!Array.isArray(v) || v.length === 0) continue;
5250
+ if (Array.isArray(residualEnums[k])) continue;
5251
+ kept[k] = v;
5252
+ }
5253
+ if (Object.keys(kept).length > 0) out.enums = kept;
5254
+ }
5255
+ return out;
5256
+ }
5257
+ function datetimeFacetToRfc3339(dt) {
5258
+ if (!dt) return void 0;
5259
+ const lo = dt.min;
5260
+ const hi = dt.max;
5261
+ if (!lo && !hi) return void 0;
5262
+ if (lo && hi) return lo === hi ? lo : `${lo}/${hi}`;
5263
+ if (lo) return `${lo}/..`;
5264
+ return `../${hi}`;
5265
+ }
5266
+ function createApiSource(kind, deps) {
5267
+ const capabilities = {
5268
+ kind: "api",
5269
+ label: "STAC API",
5270
+ countAvailable: false,
5271
+ streaming: true,
5272
+ pushdown: {
5273
+ ...emptyPushdown(),
5274
+ bbox: true,
5275
+ datetime: true,
5276
+ collection: true,
5277
+ cloudCover: true,
5278
+ gsd: true,
5279
+ platform: true,
5280
+ constellation: true,
5281
+ instruments: true
5282
+ }
5283
+ };
5284
+ let capsPromise = null;
5285
+ const getCaps = (signal) => {
5286
+ if (!capsPromise) {
5287
+ capsPromise = sniffSourceCapabilities(kind, deps, signal).catch(() => SLICE_1_CAPS);
5288
+ }
5289
+ return capsPromise;
5290
+ };
5291
+ return {
5292
+ capabilities,
5293
+ query(req) {
5294
+ return apiQueryIterable(kind, deps, req, getCaps);
5295
+ }
5296
+ };
5297
+ }
5298
+ var SLICE_1_CAPS = {
5299
+ bbox: true,
5300
+ datetime: true,
5301
+ collections: false,
5302
+ cql2: false,
5303
+ queryables: false
5304
+ };
5305
+ async function sniffSourceCapabilities(kind, deps, signal) {
5306
+ const inline = readConformsTo(kind);
5307
+ if (inline && inline.length > 0) return sniffApiCapabilities(inline);
5308
+ const json = await fetchRootJson(deps, signal);
5309
+ if (json && typeof json === "object") {
5310
+ const conformsTo = json.conformsTo;
5311
+ if (Array.isArray(conformsTo)) return sniffApiCapabilities(conformsTo);
5312
+ }
5313
+ return SLICE_1_CAPS;
5314
+ }
5315
+ function readConformsTo(kind) {
5316
+ if (kind.kind === "catalog" || kind.kind === "collection") {
5317
+ const ct = kind.payload.conformsTo;
5318
+ return Array.isArray(ct) ? ct : null;
5319
+ }
5320
+ if (kind.kind === "item-collection") {
5321
+ const ct = kind.fc.conformsTo;
5322
+ return Array.isArray(ct) ? ct : null;
5323
+ }
5324
+ return null;
5325
+ }
5326
+ async function fetchRootJson(deps, signal) {
5327
+ const href = deps.baseHref;
5328
+ if (/^https?:/i.test(href)) {
5329
+ const ownKey = deps.urlToKey ? deps.urlToKey(href) : null;
5330
+ if (ownKey !== null) {
5331
+ const buf2 = await deps.adapter.read(ownKey, void 0, void 0, signal);
5332
+ return JSON.parse(new TextDecoder().decode(buf2));
5333
+ }
5334
+ const res = await fetch(href, { signal });
5335
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${href}`);
5336
+ return await res.json();
5337
+ }
5338
+ const buf = await deps.adapter.read(href, void 0, void 0, signal);
5339
+ return JSON.parse(new TextDecoder().decode(buf));
5340
+ }
5341
+ async function* apiQueryIterable(kind, deps, req, getCaps) {
5342
+ if (req.signal.aborted) throw new DOMException("Aborted", "AbortError");
5343
+ const caps = await getCaps(req.signal);
5344
+ if (req.signal.aborted) throw new DOMException("Aborted", "AbortError");
5345
+ const native = toNativeQuery(req.filter, caps, {
5346
+ bbox: req.bbox,
5347
+ limit: req.pageSize ?? req.limit
5348
+ });
5349
+ const datetime = native.datetime ?? datetimeFacetToRfc3339(req.filter.datetime);
5350
+ const itemsQuery = {
5351
+ bbox: native.bbox ?? req.bbox,
5352
+ datetime,
5353
+ limit: native.limit ?? req.pageSize ?? req.limit,
5354
+ filter: native.filter
5355
+ };
5356
+ const residual = residualState(req.filter, caps);
5357
+ const pushedDown = subtractState(req.filter, residual);
5358
+ if (!caps.cql2) {
5359
+ itemsQuery.filter = void 0;
5360
+ } else if (itemsQuery.filter === void 0) {
5361
+ const cql = toCql2Filter(req.filter, caps);
5362
+ if (cql) itemsQuery.filter = cql;
5363
+ }
5364
+ const buffer = [];
5365
+ let pendingResolve = null;
5366
+ const push = (s) => {
5367
+ if (pendingResolve) {
5368
+ const r = pendingResolve;
5369
+ pendingResolve = null;
5370
+ r(s);
5371
+ } else {
5372
+ buffer.push(s);
5373
+ }
5374
+ };
5375
+ const next = () => {
5376
+ if (buffer.length > 0) return Promise.resolve(buffer.shift());
5377
+ return new Promise((resolve) => {
5378
+ pendingResolve = resolve;
5379
+ });
5380
+ };
5381
+ const onAbort = () => push({ kind: "error", error: new DOMException("Aborted", "AbortError") });
5382
+ req.signal.addEventListener("abort", onAbort, { once: true });
5383
+ void (async () => {
5384
+ try {
5385
+ await hydrateStacItems(kind, deps.baseHref, deps.adapter, {
5386
+ signal: req.signal,
5387
+ concurrency: deps.concurrency ?? 12,
5388
+ limit: req.limit,
5389
+ urlToKey: deps.urlToKey,
5390
+ itemsQuery,
5391
+ onBatch: (batch) => {
5392
+ if (batch.length > 0) push({ kind: "value", batch });
5393
+ }
5394
+ });
5395
+ push({ kind: "done" });
5396
+ } catch (err) {
5397
+ push({ kind: "error", error: err });
5398
+ }
5399
+ })();
5400
+ try {
5401
+ while (true) {
5402
+ const state = await next();
5403
+ if (state.kind === "value") {
5404
+ yield {
5405
+ items: state.batch,
5406
+ pushedDown,
5407
+ residual,
5408
+ done: false
3030
5409
  };
5410
+ continue;
3031
5411
  }
3032
- if (pathParts.length > 0) {
3033
- const endpoint = `${url.protocol}//${url.host}`;
3034
- return {
3035
- bucket: pathParts[0],
3036
- region: defaults.region || "us-east-1",
3037
- endpoint,
3038
- provider: defaults.provider || "s3",
3039
- prefix: pathParts.slice(1).join("/")
5412
+ if (state.kind === "done") {
5413
+ yield {
5414
+ items: [],
5415
+ pushedDown,
5416
+ residual,
5417
+ done: true
3040
5418
  };
5419
+ return;
3041
5420
  }
3042
- return {
3043
- ...defaultResult(defaults),
3044
- endpoint: `${url.protocol}//${url.host}`
3045
- };
3046
- } catch {
5421
+ throw state.error;
3047
5422
  }
5423
+ } finally {
5424
+ req.signal.removeEventListener("abort", onAbort);
3048
5425
  }
3049
- const cleaned = trimmed.replace(/^\/+|\/+$/g, "");
5426
+ }
5427
+
5428
+ // src/stac-source-static.ts
5429
+ function createStaticSource(kind, deps) {
5430
+ const capabilities = {
5431
+ kind: "static",
5432
+ label: "Static catalog",
5433
+ countAvailable: false,
5434
+ streaming: true,
5435
+ pushdown: { ...emptyPushdown() }
5436
+ };
3050
5437
  return {
3051
- bucket: cleaned,
3052
- region: defaults.region || "us-east-1",
3053
- endpoint: defaults.endpoint || "",
3054
- provider: defaults.provider || "s3",
3055
- prefix: ""
5438
+ capabilities,
5439
+ query(req) {
5440
+ return staticQueryIterable(kind, deps, req);
5441
+ }
3056
5442
  };
3057
5443
  }
3058
- function looksLikeUrl(input) {
3059
- const lower = input.trim().toLowerCase();
3060
- if (lower.startsWith("http://") || lower.startsWith("https://")) return true;
3061
- for (const scheme of Object.keys(SCHEME_MAP)) {
3062
- if (lower.startsWith(scheme)) return true;
5444
+ async function* staticQueryIterable(kind, deps, req) {
5445
+ if (req.signal.aborted) throw new DOMException("Aborted", "AbortError");
5446
+ const pushedDown = {};
5447
+ const residual = req.filter;
5448
+ const buffer = [];
5449
+ let pendingResolve = null;
5450
+ const push = (s) => {
5451
+ if (pendingResolve) {
5452
+ const r = pendingResolve;
5453
+ pendingResolve = null;
5454
+ r(s);
5455
+ } else {
5456
+ buffer.push(s);
5457
+ }
5458
+ };
5459
+ const next = () => {
5460
+ if (buffer.length > 0) return Promise.resolve(buffer.shift());
5461
+ return new Promise((resolve) => {
5462
+ pendingResolve = resolve;
5463
+ });
5464
+ };
5465
+ const onAbort = () => push({ kind: "error", error: new DOMException("Aborted", "AbortError") });
5466
+ req.signal.addEventListener("abort", onAbort, { once: true });
5467
+ void (async () => {
5468
+ try {
5469
+ await hydrateStacItems(kind, deps.baseHref, deps.adapter, {
5470
+ signal: req.signal,
5471
+ concurrency: deps.concurrency ?? 12,
5472
+ limit: req.limit,
5473
+ urlToKey: deps.urlToKey,
5474
+ onBatch: (batch) => {
5475
+ if (batch.length > 0) push({ kind: "value", batch });
5476
+ }
5477
+ });
5478
+ push({ kind: "done" });
5479
+ } catch (err) {
5480
+ push({ kind: "error", error: err });
5481
+ }
5482
+ })();
5483
+ try {
5484
+ while (true) {
5485
+ const state = await next();
5486
+ if (state.kind === "value") {
5487
+ yield {
5488
+ items: state.batch,
5489
+ pushedDown,
5490
+ residual,
5491
+ done: false
5492
+ };
5493
+ continue;
5494
+ }
5495
+ if (state.kind === "done") {
5496
+ yield { items: [], pushedDown, residual, done: true };
5497
+ return;
5498
+ }
5499
+ throw state.error;
5500
+ }
5501
+ } finally {
5502
+ req.signal.removeEventListener("abort", onAbort);
3063
5503
  }
3064
- return false;
3065
5504
  }
3066
- function describeParseResult(parsed) {
3067
- const parts = [];
3068
- if (parsed.bucket) parts.push(`bucket="${parsed.bucket}"`);
3069
- if (parsed.endpoint) parts.push(`endpoint="${parsed.endpoint}"`);
3070
- if (parsed.region && parsed.region !== "us-east-1") parts.push(`region="${parsed.region}"`);
3071
- if (parsed.provider !== "s3") parts.push(`provider=${parsed.provider}`);
3072
- if (parsed.prefix) parts.push(`prefix="${parsed.prefix}"`);
3073
- return parts.length > 0 ? `Detected: ${parts.join(", ")}` : "";
5505
+
5506
+ // src/storage-smoketest.ts
5507
+ async function smokeTestHref(href, signal) {
5508
+ let response;
5509
+ try {
5510
+ response = await fetch(href, {
5511
+ method: "GET",
5512
+ headers: { Range: "bytes=0-0" },
5513
+ signal,
5514
+ redirect: "follow"
5515
+ });
5516
+ } catch (err) {
5517
+ if (err instanceof DOMException && err.name === "AbortError") throw err;
5518
+ const reason2 = err instanceof Error ? err.message : String(err);
5519
+ return { ok: false, status: null, reason: reason2 };
5520
+ }
5521
+ if (response.ok || response.status === 206) {
5522
+ await response.body?.cancel().catch(() => {
5523
+ });
5524
+ return { ok: true };
5525
+ }
5526
+ const reason = `${response.status} ${response.statusText || ""}`.trim();
5527
+ return { ok: false, status: response.status, reason };
3074
5528
  }
3075
5529
 
3076
- // ../../src/lib/utils/wkb.ts
5530
+ // src/wkb.ts
3077
5531
  var WKB_POINT = 1;
3078
5532
  var WKB_LINESTRING = 2;
3079
5533
  var WKB_POLYGON = 3;
@@ -3284,17 +5738,29 @@ function looksLikeWKB(value) {
3284
5738
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3285
5739
  let typeInt = view.getUint32(1, le);
3286
5740
  typeInt = (typeInt & 65535) % 1e3;
3287
- return typeInt >= 1 && typeInt <= 7;
5741
+ return typeInt >= 1 && typeInt <= 6;
3288
5742
  }
3289
5743
  function findGeoColumnFromRows(rows, schema) {
3290
5744
  if (rows.length === 0) return null;
3291
- const sample = rows[0];
5745
+ const MAX_SCAN = 50;
5746
+ const scan = rows.slice(0, MAX_SCAN);
5747
+ const firstNonNull = (key) => {
5748
+ for (const row of scan) {
5749
+ const v = row[key];
5750
+ if (v !== null && v !== void 0) return v;
5751
+ }
5752
+ return void 0;
5753
+ };
3292
5754
  for (const f of schema) {
3293
5755
  const t = f.type.toLowerCase();
3294
5756
  const isBinary = t.includes("blob") || t.includes("binary") || t.includes("bytea");
3295
- if (isBinary && looksLikeWKB(sample[f.name])) return f.name;
5757
+ if (isBinary && looksLikeWKB(firstNonNull(f.name))) return f.name;
3296
5758
  }
3297
- for (const [key, value] of Object.entries(sample)) {
5759
+ const keys = /* @__PURE__ */ new Set();
5760
+ for (const row of scan) for (const k of Object.keys(row)) keys.add(k);
5761
+ for (const key of keys) {
5762
+ const value = firstNonNull(key);
5763
+ if (value === void 0) continue;
3298
5764
  if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
3299
5765
  if (looksLikeWKB(value)) return key;
3300
5766
  }
@@ -3328,6 +5794,6 @@ function isWKT(value) {
3328
5794
  return WKT_TYPES.some((t) => s.startsWith(t) || s.startsWith(`MULTI${t}`));
3329
5795
  }
3330
5796
 
3331
- export { COPY_FEEDBACK_MS, DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, LAYER_HUE_MULTIPLIER, MAX_QUERY_HISTORY_ENTRIES, PROVIDERS, PROVIDER_IDS, QueryCancelledError, SF_LABELS, SQL_PREVIEW_LENGTH, STAC_GEOPARQUET_REQUIRED_COLUMNS, STORAGE_KEYS, UrlAdapter, VIEWER_DIR_EXTENSIONS, WGS84_CODES, buildDataTypeLabel, buildDuckDbSource, buildEndpointFromTemplate, buildGeoArrowTables, buildProviderBaseUrl, clampBounds, classifyType, describeParseResult, escapeCsvField, exportToCsv, exportToJson, extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, findGeoColumn, findGeoColumnFromRows, flattenStacBbox, formatDate, formatFileSize, formatValue, generateHexDump, getAccessMode, getDuckDbReadFn, getFileExtension, getFileTypeInfo, getMimeType, getNativeScheme, getProvider, getViewerKind, handleLoadError, interpolateTemplates, isCloudNativeFormat, isGcsProvider, isPubliclyStreamable, isQueryable, isStacGeoparquetSchema, jsonReplacerBigInt, loadFromStorage, looksLikeUrl, markSqlBlocks, normalizeGeomType, parseMarkdownDocument, parseStorageUrl, parseWKB, persistToStorage, pickStacPrimaryAsset, readParquetMetadata, resolveCloudUrl, resolveProviderEndpoint, resolveStacAssetHref, safeClamp, safeDecodeURIComponent, serializeToCsv, serializeToJson, sortFileEntries, stacRowToItem, toBinary, toggleSortField, typeBadgeClass, typeColor, typeLabel };
5797
+ export { COPY_FEEDBACK_MS, DATETIME_HISTOGRAM_BINS, DATETIME_HISTOGRAM_BINS_MAX, DEFAULT_APP_CONFIG, DEFAULT_AWS_REGION, DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, FIRST_FEATURE_FLY_ZOOM, LAYER_HUE_MULTIPLIER, LruCache, MAX_QUERY_HISTORY_ENTRIES, MarkdownSqlContext, PRESETS, PROVIDERS, PROVIDER_IDS, QueryCancelledError, SF_LABELS, SQL_PREVIEW_LENGTH, STAC_API_PATH_RE, STAC_COG_ASSET_KEYS, STAC_GEOPARQUET_REQUIRED_COLUMNS, STORAGE_KEYS, TILE_DEBOUNCE_MS, UrlAdapter, VIEWER_DIR_EXTENSIONS, WGS84_CODES, absolutizeHref, allChannelsBand0, applyFacets, applyPreset, applyStacItemStorageHints, applyStorageHintsToConnection, attachPixelInspector, availablePresets, buildDataTypeLabel, buildDuckDbSource, buildEndpointFromTemplate, buildFacets, buildGeoArrowTables, buildMosaicSourceMeta, buildProviderBaseUrl, buildTransformExpr, clampBounds, classifyStac, classifyType, classifyUrl, coerceBool, coercePositiveInt, coerceString, coerceTheme, compositeFromUrl, compositeToUrl, connectionIdentityKey, copyToClipboard, createApiSource, createStaticSource, describeParseResult, detectHostBucket, detectMosaicCapable, detectMultiCogCapable, detectStorageExtensionVersion, emptyFacetState, emptyPushdown, emptyStorageHints, escapeCsvField, exportToCsv, exportToJson, extractBounds, extractCogAssets, extractEpsgFromGeoMeta, extractGeometryTypes, extractItemView, extractMosaicAssets, extractRasterBandAssets, extractSentinelBandAssets, extractStorageHints, findGeoColumn, findGeoColumnFromRows, flattenStacBbox, formatDate, formatFileSize, formatValue, generateHexDump, getAccessMode, getDuckDbReadFn, getFileExtension, getFileTypeInfo, getMimeType, getNativeScheme, getProvider, getViewerKind, handleLoadError, hasActiveFilters, hasCompositableBands, hasRgbBands, hasStacItemsEndpoint, hydrateStacItems, interpolateTemplates, isAbortError, isCloudNativeFormat, isGcsProvider, isKnownBucketHost, isPubliclyStreamable, isQueryable, isSameConnectionIdentity, isSingleAssetComposite, isStacCatalog, isStacCollection, isStacFeatureCollection, isStacGeoparquetSchema, isStacItem, isWgs84, isWgs84Crs, jsonReplacerBigInt, loadFromStorage, looksLikeUrl, markSqlBlocks, mergeAppConfig, normalizeEndpoint, normalizeGeomType, normalizeProvider, parseGeometryTypeCrs, parseMarkdownDocument, parseStorageUrl, parseVisibilityParam, parseWKB, persistToStorage, pickCogAssetHref, pickGranularity, pickNaturalColorComposite, pickStacPrimaryAsset, presetMatchesComposite, readParquetMetadata, renderNotebook, residualState, resolveBandSlotAssetKey, resolveBasemap, resolveCloudUrl, resolvePresetComposite, resolveProviderEndpoint, resolveSetting, resolveStacAssetHref, safeClamp, safeDecodeURIComponent, serializeToCsv, serializeToJson, smokeTestHref, sniffApiCapabilities, sortFileEntries, sortViews, spatialCellKey, stacItemBbox, stacRowToItem, syntheticSelfAsset, toBinary, toCql2Filter, toNativeQuery, toggleSortField, typeBadgeClass, typeColor, typeLabel, wireCodeCopyButtons, wrapWkbWithCrs };
3332
5798
  //# sourceMappingURL=index.js.map
3333
5799
  //# sourceMappingURL=index.js.map