@walkthru-earth/objex-utils 1.1.0 → 1.2.1

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/README.md CHANGED
@@ -3,51 +3,50 @@
3
3
  [![npm](https://img.shields.io/npm/v/@walkthru-earth/objex-utils?color=cb3837)](https://www.npmjs.com/package/@walkthru-earth/objex-utils)
4
4
  [![License: CC BY 4.0](https://img.shields.io/badge/license-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/)
5
5
 
6
- Pure TypeScript utilities extracted from [objex](https://github.com/walkthru-earth/objex) zero Svelte dependency. Works with any JS framework or Node.js.
6
+ Pure TypeScript utilities extracted from [objex](https://github.com/walkthru-earth/objex). Zero Svelte dependency. Works with any JS framework or Node 18+.
7
+
8
+ Built for high-performance geospatial pipelines: WKB parsing, GeoArrow table construction, GeoParquet footer reading via range requests, cloud URL parsing, and a 200+ extension file-type registry.
7
9
 
8
10
  ## Install
9
11
 
10
12
  ```bash
11
13
  pnpm add @walkthru-earth/objex-utils
14
+ # or
15
+ npm install @walkthru-earth/objex-utils
12
16
  ```
13
17
 
14
- ## Usage
18
+ ## At a glance
15
19
 
16
20
  ```ts
17
21
  import {
18
- // WKB / Geometry
22
+ // WKB / GeoArrow
19
23
  parseWKB,
20
24
  findGeoColumn,
21
25
  buildGeoArrowTables,
22
26
 
23
- // Storage URLs
24
- parseStorageUrl,
25
- looksLikeUrl,
26
-
27
- // Parquet metadata
27
+ // Parquet metadata (hyparquet, range requests)
28
28
  readParquetMetadata,
29
29
  extractEpsgFromGeoMeta,
30
30
  extractBounds,
31
31
 
32
- // File type registry
32
+ // Storage URLs
33
+ parseStorageUrl,
34
+ resolveCloudUrl,
35
+ looksLikeUrl,
36
+
37
+ // File-type registry
33
38
  getFileTypeInfo,
34
39
  getViewerKind,
35
- getMimeType,
40
+ getDuckDbReadFn,
36
41
  isQueryable,
37
42
 
38
- // Formatting
43
+ // Formatting / classification / hex / CSV / JSON
39
44
  formatFileSize,
40
- formatDate,
41
45
  formatValue,
42
- jsonReplacerBigInt,
43
-
44
- // Column types
45
46
  classifyType,
46
- typeColor,
47
- typeLabel,
48
-
49
- // Hex dump
50
47
  generateHexDump,
48
+ serializeToCsv,
49
+ serializeToJson,
51
50
 
52
51
  // Error handling
53
52
  handleLoadError,
@@ -59,33 +58,41 @@ import {
59
58
  } from '@walkthru-earth/objex-utils';
60
59
  ```
61
60
 
62
- ## Exports
61
+ ## Documentation
62
+
63
+ Full per-module developer reference lives in [`docs/`](./docs/README.md). Each page lists the exact TypeScript signature, parameter semantics, return shape, peer-dependency requirements, and non-obvious behavior.
64
+
65
+ | Page | Covers |
66
+ |------|--------|
67
+ | [`docs/geometry.md`](./docs/geometry.md) | WKB parser, GeoArrow builder, geometry-column detection |
68
+ | [`docs/cog.md`](./docs/cog.md) | Cloud-Optimized GeoTIFF pipeline helpers, band configs, color ramps |
69
+ | [`docs/parquet-metadata.md`](./docs/parquet-metadata.md) | `readParquetMetadata` + CRS / bounds / geometry-type extractors |
70
+ | [`docs/storage.md`](./docs/storage.md) | URL parsing, provider registry, `StorageAdapter`, `UrlAdapter` |
71
+ | [`docs/query-engine.md`](./docs/query-engine.md) | `QueryEngine` interface + handle / result types |
72
+ | [`docs/file-types.md`](./docs/file-types.md) | File-type registry: `getFileTypeInfo`, `getViewerKind`, `getDuckDbReadFn`, … |
73
+ | [`docs/formatting.md`](./docs/formatting.md) | Display formatters, column-type classification, hex dump, CSV/JSON export |
74
+ | [`docs/file-sort.md`](./docs/file-sort.md) | `sortFileEntries`, `toggleSortField` |
75
+ | [`docs/markdown-sql.md`](./docs/markdown-sql.md) | Markdown + SQL block parsing (Evidence-compatible) |
76
+ | [`docs/local-storage.md`](./docs/local-storage.md) | SSR-safe `loadFromStorage` / `persistToStorage` |
77
+ | [`docs/errors.md`](./docs/errors.md) | `handleLoadError` |
78
+ | [`docs/types-constants.md`](./docs/types-constants.md) | `Connection`, `Tab`, `FileEntry`, `WriteResult`, `Theme`, shared constants |
63
79
 
64
- | Export | Description |
65
- |--------|-------------|
66
- | `parseWKB()` | Parse WKB binary into coordinates with geometry type classification |
67
- | `findGeoColumn()` | 5-priority heuristic to detect geometry columns in tabular data |
68
- | `buildGeoArrowTables()` | Convert WKB arrays to GeoArrow tables for deck.gl rendering |
69
- | `parseStorageUrl()` | Parse S3/GCS/Azure/R2 URLs into provider, bucket, key |
70
- | `readParquetMetadata()` | Read Parquet file metadata via HTTP range requests (hyparquet) |
71
- | `getFileTypeInfo()` | Map file extensions to viewer kind, category, icon, and MIME type |
72
- | `formatFileSize()` | Human-readable file sizes (1024-based: KB, MB, GB) |
73
- | `formatValue()` | Format any value for display (handles BigInt, Date, objects, null) |
74
- | `generateHexDump()` | Generate hex dump rows from binary data |
75
- | `classifyType()` | Classify SQL/Arrow column types into categories |
76
- | `handleLoadError()` | Normalize errors, silently skip AbortError |
77
- | `WGS84_CODES` | Set of EPSG codes considered WGS84 (4326, 4979) |
80
+ ## Optional peer dependencies
78
81
 
79
- ## Optional Peer Dependencies
82
+ Heavy dependencies are **optional** peers. Install only what you use.
80
83
 
81
- Heavy dependencies are optional only install what you use:
84
+ | Peer | Required by |
85
+ |------|-------------|
86
+ | `apache-arrow >=14` | `buildGeoArrowTables` |
87
+ | `hyparquet >=1.25` | `readParquetMetadata` and friends |
88
+ | `hyparquet-compressors >=1.1` | SNAPPY / ZSTD / GZIP / LZ4 / BROTLI support in `readParquetMetadata` |
89
+ | `yaml >=2` | `parseMarkdownDocument` (lazy-loaded — only when frontmatter is present) |
82
90
 
83
- - `apache-arrow` required for `buildGeoArrowTables()`
84
- - `hyparquet` + `hyparquet-compressors` — required for `readParquetMetadata()`
91
+ As of v1.2 the `yaml` dependency is imported dynamically inside `parseMarkdownDocument`. Consumers who never call that function do not need `yaml` at all. Before v1.2 the bundle failed to load without `yaml` even for unrelated imports.
85
92
 
86
93
  ## Related
87
94
 
88
- - [`@walkthru-earth/objex`](https://www.npmjs.com/package/@walkthru-earth/objex) — Full Svelte 5 component library with viewers, stores, and query engine
95
+ - [`@walkthru-earth/objex`](https://www.npmjs.com/package/@walkthru-earth/objex) — Full Svelte 5 component library with viewers, stores, and query engine.
89
96
 
90
97
  ## License
91
98
 
package/dist/index.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ require('@developmentseed/epsg/all');
4
+ require('@developmentseed/epsg/all.csv.gz?url');
5
+ require('@developmentseed/geotiff');
6
+ require('@developmentseed/proj');
7
+ require('proj4');
3
8
  var apacheArrow = require('apache-arrow');
4
- var YAML = require('yaml');
5
-
6
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
-
8
- var YAML__default = /*#__PURE__*/_interopDefault(YAML);
9
9
 
10
10
  // ../../src/lib/constants.ts
11
11
  var STORAGE_KEYS = {
@@ -14,7 +14,7 @@ var STORAGE_KEYS = {
14
14
  QUERY_HISTORY: "obstore-explore-query-history"
15
15
  };
16
16
  var WGS84_CODES = /* @__PURE__ */ new Set([4326, 4979]);
17
- var DEFAULT_TARGET_CRS = "EPSG:4326";
17
+ var DEFAULT_TARGET_CRS = "OGC:CRS84";
18
18
  var DUCKDB_INIT_TIMEOUT_MS = 3e4;
19
19
  var MAX_QUERY_HISTORY_ENTRIES = 200;
20
20
  var SQL_PREVIEW_LENGTH = 120;
@@ -894,6 +894,16 @@ var EXTENSIONS = {
894
894
  duckdbReadFn: null,
895
895
  mimeType: "application/octet-stream"
896
896
  },
897
+ ".ducklake": {
898
+ icon: "Database",
899
+ color: "text-teal-600 dark:text-teal-400",
900
+ label: "DuckLake",
901
+ category: "database",
902
+ viewer: "database",
903
+ queryable: true,
904
+ duckdbReadFn: null,
905
+ mimeType: "application/octet-stream"
906
+ },
897
907
  ".sqlite": {
898
908
  icon: "Database",
899
909
  color: "text-sky-600 dark:text-sky-400",
@@ -1041,7 +1051,7 @@ function buildDuckDbSource(pathOrExt, url) {
1041
1051
  const readFn = EXTENSIONS[ext]?.duckdbReadFn ?? "read_parquet";
1042
1052
  return `${readFn}('${url}')`;
1043
1053
  }
1044
- var CLOUD_NATIVE_EXTS = /* @__PURE__ */ new Set([".parquet", ".geoparquet", ".gpq", ".gparquet"]);
1054
+ var CLOUD_NATIVE_EXTS = /* @__PURE__ */ new Set([".parquet", ".geoparquet", ".gpq", ".gparquet", ".ducklake"]);
1045
1055
  function isCloudNativeFormat(pathOrExt) {
1046
1056
  const ext = pathOrExt.includes(".") ? `.${pathOrExt.split(".").pop().toLowerCase()}` : "";
1047
1057
  return CLOUD_NATIVE_EXTS.has(ext);
@@ -1347,6 +1357,11 @@ function buildEndpointFromTemplate(id, region) {
1347
1357
  if (!def?.endpointTemplate) return "";
1348
1358
  return def.endpointTemplate.replace("{region}", region);
1349
1359
  }
1360
+ function resolveProviderEndpoint(provider, region) {
1361
+ const def = PROVIDERS[provider];
1362
+ if (!def?.endpointTemplate) return "";
1363
+ return buildEndpointFromTemplate(provider, region || def.defaultRegion);
1364
+ }
1350
1365
  function buildProviderBaseUrl(provider, endpoint, bucket, region) {
1351
1366
  if (endpoint) {
1352
1367
  return `${endpoint.replace(/\/$/, "")}/${bucket}`;
@@ -1361,6 +1376,15 @@ function buildProviderBaseUrl(provider, endpoint, bucket, region) {
1361
1376
  function isGcsProvider(provider, endpoint) {
1362
1377
  return provider === "gcs" || !!endpoint && /storage\.googleapis\.com/i.test(endpoint);
1363
1378
  }
1379
+ function getAccessMode(conn) {
1380
+ if (conn.provider === "azure") return "sas-https";
1381
+ if (conn.anonymous) return "public-https";
1382
+ return "signed-s3";
1383
+ }
1384
+ function isPubliclyStreamable(conn) {
1385
+ const mode = getAccessMode(conn);
1386
+ return mode === "public-https" || mode === "sas-https";
1387
+ }
1364
1388
 
1365
1389
  // ../../src/lib/storage/url-adapter.ts
1366
1390
  var UrlAdapter = class {
@@ -1438,6 +1462,28 @@ function resolveCloudUrl(url) {
1438
1462
  }
1439
1463
  return url;
1440
1464
  }
1465
+ var SF_LABELS = {
1466
+ 1: "uint",
1467
+ 2: "int",
1468
+ 3: "float",
1469
+ 4: "void",
1470
+ 5: "complex int",
1471
+ 6: "complex float"
1472
+ };
1473
+ function safeClamp(v, lo, hi, fallback) {
1474
+ return Number.isFinite(v) ? Math.max(lo, Math.min(hi, v)) : fallback;
1475
+ }
1476
+ function clampBounds(b) {
1477
+ return {
1478
+ west: safeClamp(b.west, -180, 180, -180),
1479
+ south: safeClamp(b.south, -85.051129, 85.051129, -85.051129),
1480
+ east: safeClamp(b.east, -180, 180, 180),
1481
+ north: safeClamp(b.north, -85.051129, 85.051129, 85.051129)
1482
+ };
1483
+ }
1484
+ function buildDataTypeLabel(sampleFormat, bitsPerSample) {
1485
+ return `${SF_LABELS[sampleFormat] ?? `sf${sampleFormat}`}${bitsPerSample ?? ""}`;
1486
+ }
1441
1487
 
1442
1488
  // ../../src/lib/utils/column-types.ts
1443
1489
  var NUMBER_TYPES = [
@@ -1608,6 +1654,17 @@ function formatValue(value) {
1608
1654
  }
1609
1655
 
1610
1656
  // ../../src/lib/utils/export.ts
1657
+ function triggerDownload(content, filename, mimeType) {
1658
+ const blob = new Blob([content], { type: mimeType });
1659
+ const url = URL.createObjectURL(blob);
1660
+ const a = document.createElement("a");
1661
+ a.href = url;
1662
+ a.download = filename;
1663
+ document.body.appendChild(a);
1664
+ a.click();
1665
+ document.body.removeChild(a);
1666
+ URL.revokeObjectURL(url);
1667
+ }
1611
1668
  function formatCellValue(value) {
1612
1669
  if (value === null || value === void 0) return "";
1613
1670
  if (value instanceof Date) return value.toISOString();
@@ -1642,6 +1699,20 @@ function serializeToJson(columns, rows) {
1642
1699
  });
1643
1700
  return JSON.stringify(data, jsonReplacerBigInt, 2);
1644
1701
  }
1702
+ function exportToCsv(columns, rows, filename) {
1703
+ triggerDownload(
1704
+ serializeToCsv(columns, rows),
1705
+ filename.endsWith(".csv") ? filename : `${filename}.csv`,
1706
+ "text/csv;charset=utf-8;"
1707
+ );
1708
+ }
1709
+ function exportToJson(columns, rows, filename) {
1710
+ triggerDownload(
1711
+ serializeToJson(columns, rows),
1712
+ filename.endsWith(".json") ? filename : `${filename}.json`,
1713
+ "application/json"
1714
+ );
1715
+ }
1645
1716
 
1646
1717
  // ../../src/lib/utils/file-sort.ts
1647
1718
  function sortFileEntries(entries, config) {
@@ -2289,13 +2360,16 @@ function persistToStorage(key, value) {
2289
2360
  } catch {
2290
2361
  }
2291
2362
  }
2292
- function parseMarkdownDocument(markdown) {
2363
+
2364
+ // ../../src/lib/utils/markdown-sql.ts
2365
+ async function parseMarkdownDocument(markdown) {
2293
2366
  let frontmatter = {};
2294
2367
  let content = markdown;
2295
2368
  const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---\n/);
2296
2369
  if (fmMatch) {
2297
2370
  try {
2298
- frontmatter = YAML__default.default.parse(fmMatch[1]) || {};
2371
+ const { default: YAML } = await import('yaml');
2372
+ frontmatter = YAML.parse(fmMatch[1]) || {};
2299
2373
  } catch {
2300
2374
  }
2301
2375
  content = markdown.slice(fmMatch[0].length);
@@ -2965,7 +3039,10 @@ var GEO_TYPE_KEYWORDS = [
2965
3039
  "geometrycollection",
2966
3040
  "sdo_geometry"
2967
3041
  ];
2968
- var GEO_NAME_HINTS = ["geom", "geometry", "geo_", "_geo", "wkb", "wkt", "shape", "spatial"];
3042
+ var GEO_NAME_HINTS = ["geom", "geometry", "wkb", "wkt", "shape", "spatial", "geo"];
3043
+ function tokenizeColumnName(name) {
3044
+ return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([a-z])([0-9])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
3045
+ }
2969
3046
  var GEOJSON_TYPES = [
2970
3047
  "Point",
2971
3048
  "LineString",
@@ -2995,14 +3072,14 @@ function findGeoColumn(schema) {
2995
3072
  if (GEO_NAMES.includes(f.name.toLowerCase())) return f.name;
2996
3073
  }
2997
3074
  for (const f of schema) {
2998
- const n = f.name.toLowerCase();
3075
+ const tokens = tokenizeColumnName(f.name);
2999
3076
  const t = f.type.toLowerCase();
3000
3077
  const isBinary = t.includes("blob") || t.includes("binary") || t.includes("bytea");
3001
- if (isBinary && GEO_NAME_HINTS.some((hint) => n.includes(hint))) return f.name;
3078
+ if (isBinary && tokens.some((tok) => GEO_NAME_HINTS.includes(tok))) return f.name;
3002
3079
  }
3003
3080
  for (const f of schema) {
3004
- const n = f.name.toLowerCase();
3005
- if (GEO_NAME_HINTS.some((hint) => n.includes(hint))) return f.name;
3081
+ const tokens = tokenizeColumnName(f.name);
3082
+ if (tokens.some((tok) => GEO_NAME_HINTS.includes(tok))) return f.name;
3006
3083
  }
3007
3084
  return null;
3008
3085
  }
@@ -3067,18 +3144,23 @@ exports.MAX_QUERY_HISTORY_ENTRIES = MAX_QUERY_HISTORY_ENTRIES;
3067
3144
  exports.PROVIDERS = PROVIDERS;
3068
3145
  exports.PROVIDER_IDS = PROVIDER_IDS;
3069
3146
  exports.QueryCancelledError = QueryCancelledError;
3147
+ exports.SF_LABELS = SF_LABELS;
3070
3148
  exports.SQL_PREVIEW_LENGTH = SQL_PREVIEW_LENGTH;
3071
3149
  exports.STORAGE_KEYS = STORAGE_KEYS;
3072
3150
  exports.UrlAdapter = UrlAdapter;
3073
3151
  exports.VIEWER_DIR_EXTENSIONS = VIEWER_DIR_EXTENSIONS;
3074
3152
  exports.WGS84_CODES = WGS84_CODES;
3153
+ exports.buildDataTypeLabel = buildDataTypeLabel;
3075
3154
  exports.buildDuckDbSource = buildDuckDbSource;
3076
3155
  exports.buildEndpointFromTemplate = buildEndpointFromTemplate;
3077
3156
  exports.buildGeoArrowTables = buildGeoArrowTables;
3078
3157
  exports.buildProviderBaseUrl = buildProviderBaseUrl;
3158
+ exports.clampBounds = clampBounds;
3079
3159
  exports.classifyType = classifyType;
3080
3160
  exports.describeParseResult = describeParseResult;
3081
3161
  exports.escapeCsvField = escapeCsvField;
3162
+ exports.exportToCsv = exportToCsv;
3163
+ exports.exportToJson = exportToJson;
3082
3164
  exports.extractBounds = extractBounds;
3083
3165
  exports.extractEpsgFromGeoMeta = extractEpsgFromGeoMeta;
3084
3166
  exports.extractGeometryTypes = extractGeometryTypes;
@@ -3088,6 +3170,7 @@ exports.formatDate = formatDate;
3088
3170
  exports.formatFileSize = formatFileSize;
3089
3171
  exports.formatValue = formatValue;
3090
3172
  exports.generateHexDump = generateHexDump;
3173
+ exports.getAccessMode = getAccessMode;
3091
3174
  exports.getDuckDbReadFn = getDuckDbReadFn;
3092
3175
  exports.getFileExtension = getFileExtension;
3093
3176
  exports.getFileTypeInfo = getFileTypeInfo;
@@ -3099,6 +3182,7 @@ exports.handleLoadError = handleLoadError;
3099
3182
  exports.interpolateTemplates = interpolateTemplates;
3100
3183
  exports.isCloudNativeFormat = isCloudNativeFormat;
3101
3184
  exports.isGcsProvider = isGcsProvider;
3185
+ exports.isPubliclyStreamable = isPubliclyStreamable;
3102
3186
  exports.isQueryable = isQueryable;
3103
3187
  exports.jsonReplacerBigInt = jsonReplacerBigInt;
3104
3188
  exports.loadFromStorage = loadFromStorage;
@@ -3111,6 +3195,8 @@ exports.parseWKB = parseWKB;
3111
3195
  exports.persistToStorage = persistToStorage;
3112
3196
  exports.readParquetMetadata = readParquetMetadata;
3113
3197
  exports.resolveCloudUrl = resolveCloudUrl;
3198
+ exports.resolveProviderEndpoint = resolveProviderEndpoint;
3199
+ exports.safeClamp = safeClamp;
3114
3200
  exports.safeDecodeURIComponent = safeDecodeURIComponent;
3115
3201
  exports.serializeToCsv = serializeToCsv;
3116
3202
  exports.serializeToJson = serializeToJson;