@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 +46 -39
- package/dist/index.cjs +100 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -8
- package/dist/index.d.ts +106 -8
- package/dist/index.js +91 -10
- package/dist/index.js.map +1 -1
- package/docs/README.md +102 -0
- package/docs/cog.md +92 -0
- package/docs/errors.md +34 -0
- package/docs/file-sort.md +67 -0
- package/docs/file-types.md +141 -0
- package/docs/formatting.md +192 -0
- package/docs/geometry.md +198 -0
- package/docs/local-storage.md +51 -0
- package/docs/markdown-sql.md +109 -0
- package/docs/parquet-metadata.md +133 -0
- package/docs/query-engine.md +140 -0
- package/docs/storage.md +253 -0
- package/docs/types-constants.md +173 -0
- package/package.json +10 -4
package/docs/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# `@walkthru-earth/objex-utils` Developer Docs
|
|
2
|
+
|
|
3
|
+
Professional reference documentation for every public export of the package.
|
|
4
|
+
|
|
5
|
+
Each page lists the exact TypeScript signature, parameter semantics, return shape, peer-dependency requirements, and non-obvious behavior. Intended as the single source of truth for integrators.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @walkthru-earth/objex-utils
|
|
11
|
+
# or
|
|
12
|
+
npm install @walkthru-earth/objex-utils
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Minimum: ES2022, modern browser or Node 18+. Ships both ESM (`dist/index.js`) and CJS (`dist/index.cjs`) with full `.d.ts`.
|
|
16
|
+
|
|
17
|
+
## Optional peer dependencies
|
|
18
|
+
|
|
19
|
+
All heavy dependencies are **optional** peer dependencies. Install only the ones you touch.
|
|
20
|
+
|
|
21
|
+
| Peer | Version | Required by |
|
|
22
|
+
|------|---------|-------------|
|
|
23
|
+
| `apache-arrow` | `>=14` | `buildGeoArrowTables`, `normalizeGeomType` (indirectly) |
|
|
24
|
+
| `hyparquet` | `>=1.25` | `readParquetMetadata` and its extractors |
|
|
25
|
+
| `hyparquet-compressors` | `>=1.1` | `readParquetMetadata` (for SNAPPY/ZSTD/GZIP/etc.) |
|
|
26
|
+
| `yaml` | `>=2` | `parseMarkdownDocument` (lazy-loaded, only when YAML frontmatter is present) |
|
|
27
|
+
|
|
28
|
+
As of v1.2, `yaml` is loaded via dynamic `import()` inside `parseMarkdownDocument`. Consumers who do not call that function never touch `yaml` at load time.
|
|
29
|
+
|
|
30
|
+
## Reference pages
|
|
31
|
+
|
|
32
|
+
| Page | Covers |
|
|
33
|
+
|------|--------|
|
|
34
|
+
| [`geometry.md`](./geometry.md) | WKB parser, GeoArrow table builder, geometry-column detection |
|
|
35
|
+
| [`cog.md`](./cog.md) | Cloud-Optimized GeoTIFF helpers (pipeline selection, band configs, color ramps, bounds clamping) |
|
|
36
|
+
| [`parquet-metadata.md`](./parquet-metadata.md) | `readParquetMetadata` + CRS / bounds / geometry-types extractors |
|
|
37
|
+
| [`storage.md`](./storage.md) | URL parsing (`parseStorageUrl`, `resolveCloudUrl`), provider registry, `StorageAdapter` interface, `UrlAdapter` |
|
|
38
|
+
| [`query-engine.md`](./query-engine.md) | `QueryEngine` interface and associated result/handle types |
|
|
39
|
+
| [`file-types.md`](./file-types.md) | File-type registry (`getFileTypeInfo`, `getViewerKind`, `getDuckDbReadFn`, ...) |
|
|
40
|
+
| [`formatting.md`](./formatting.md) | Display formatters, column-type classification, hex dump, CSV/JSON export |
|
|
41
|
+
| [`file-sort.md`](./file-sort.md) | `sortFileEntries`, `toggleSortField` |
|
|
42
|
+
| [`markdown-sql.md`](./markdown-sql.md) | Markdown + SQL block parsing (Evidence-compatible syntax) |
|
|
43
|
+
| [`local-storage.md`](./local-storage.md) | SSR-safe `loadFromStorage` / `persistToStorage` |
|
|
44
|
+
| [`errors.md`](./errors.md) | `handleLoadError` |
|
|
45
|
+
| [`types-constants.md`](./types-constants.md) | `Connection`, `Tab`, `FileEntry`, `WriteResult`, `Theme`, shared constants |
|
|
46
|
+
|
|
47
|
+
## Quick recipes
|
|
48
|
+
|
|
49
|
+
### Render a GeoParquet file on deck.gl
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import {
|
|
53
|
+
readParquetMetadata,
|
|
54
|
+
extractEpsgFromGeoMeta,
|
|
55
|
+
extractGeometryTypes,
|
|
56
|
+
buildGeoArrowTables
|
|
57
|
+
} from '@walkthru-earth/objex-utils';
|
|
58
|
+
|
|
59
|
+
// 1. Read footer metadata (~512KB range requests, no DuckDB)
|
|
60
|
+
const meta = await readParquetMetadata(url);
|
|
61
|
+
const sourceCrs = extractEpsgFromGeoMeta(meta.geo!); // e.g. 'EPSG:27700' or null
|
|
62
|
+
|
|
63
|
+
// 2. Get WKB buffers + attributes from your SQL engine (DuckDB-WASM, etc.)
|
|
64
|
+
const { wkbArrays, attributes } = await myEngine.queryForMap(...);
|
|
65
|
+
|
|
66
|
+
// 3. Build GeoArrow tables (one per geometry type)
|
|
67
|
+
const tables = buildGeoArrowTables(wkbArrays, attributes);
|
|
68
|
+
|
|
69
|
+
// 4. Feed into deck.gl GeoArrowLayer(s)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Parse a cloud storage URL a user pasted
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { parseStorageUrl, looksLikeUrl, describeParseResult } from '@walkthru-earth/objex-utils';
|
|
76
|
+
|
|
77
|
+
const parsed = parseStorageUrl(input);
|
|
78
|
+
// { provider: 's3', bucket: 'my-bucket', region: 'us-east-1', endpoint: '...', prefix: '...' }
|
|
79
|
+
|
|
80
|
+
console.log(describeParseResult(parsed));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Read a binary blob as a hex dump
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { generateHexDump } from '@walkthru-earth/objex-utils';
|
|
87
|
+
|
|
88
|
+
const rows = generateHexDump(bytes);
|
|
89
|
+
// rows: [{ offset: '00000000', hex: ['48','65','6c',...], ascii: 'Hello...' }, ...]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Upstream source
|
|
93
|
+
|
|
94
|
+
All modules re-export from `src/lib/` in the [objex](https://github.com/walkthru-earth/objex) monorepo. The re-export list lives at [`packages/objex-utils/src/index.ts`](../src/index.ts).
|
|
95
|
+
|
|
96
|
+
## Versioning & releases
|
|
97
|
+
|
|
98
|
+
Both `@walkthru-earth/objex` and `@walkthru-earth/objex-utils` ship together via Changesets with a `fixed` config, so their versions stay in lockstep. See [`RELEASE.md`](../../../RELEASE.md) in the monorepo root.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
package/docs/cog.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Cloud-Optimized GeoTIFF (COG) helpers
|
|
2
|
+
|
|
3
|
+
Pure, framework-agnostic helpers for working with Cloud-Optimized GeoTIFF metadata and bounds. No Svelte, MapLibre, deck.gl, or GeoTIFF library dependency.
|
|
4
|
+
|
|
5
|
+
Source: `src/lib/utils/cog.ts`.
|
|
6
|
+
|
|
7
|
+
> The render-pipeline helpers (`selectCogPipeline`, `createConfigurableGetTileData`, `normalizeCogGeotiff`, `createEpsgResolver`, `fitCogBounds`, `renderNonTiledBitmap`, etc.) live in the same source file but are **not** re-exported from `@walkthru-earth/objex-utils` because they pull in `@developmentseed/deck.gl-geotiff`, `@developmentseed/geotiff`, `@developmentseed/proj`, `proj4`, and `maplibre-gl`. If you need them, depend on the full Svelte package [`@walkthru-earth/objex`](https://www.npmjs.com/package/@walkthru-earth/objex) (they are re-exported from `src/lib/index.ts`) and install those optional peers yourself.
|
|
8
|
+
|
|
9
|
+
## Types
|
|
10
|
+
|
|
11
|
+
### `GeoBounds`
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface GeoBounds {
|
|
15
|
+
west: number;
|
|
16
|
+
south: number;
|
|
17
|
+
east: number;
|
|
18
|
+
north: number;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### `CogInfo`
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
interface CogInfo {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
bandCount: number;
|
|
29
|
+
dataType: string; // e.g. 'uint8', 'float32', 'int16'
|
|
30
|
+
bounds: GeoBounds;
|
|
31
|
+
downsampled?: boolean; // true when renderNonTiledBitmap decimated the array
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Constants
|
|
36
|
+
|
|
37
|
+
### `SF_LABELS`
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const SF_LABELS: Record<number, string> = {
|
|
41
|
+
1: 'uint', 2: 'int', 3: 'float', 4: 'void',
|
|
42
|
+
5: 'complex int', 6: 'complex float',
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Human labels for the GeoTIFF SampleFormat tag.
|
|
47
|
+
|
|
48
|
+
## Functions
|
|
49
|
+
|
|
50
|
+
### `safeClamp(v, lo, hi, fallback)`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
function safeClamp(v: number, lo: number, hi: number, fallback: number): number
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Clamp `v` into `[lo, hi]`. If `v` is `NaN` or `±Infinity`, return `fallback`. Use instead of `Math.min(hi, Math.max(lo, v))` — NaN would otherwise propagate silently.
|
|
57
|
+
|
|
58
|
+
### `clampBounds(b)`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
function clampBounds(b: GeoBounds): GeoBounds
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Clamp geographic bounds to valid MapLibre Web Mercator range: ±180° longitude, ±85.051129° latitude.
|
|
65
|
+
|
|
66
|
+
### `buildDataTypeLabel(sampleFormat, bitsPerSample)`
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
function buildDataTypeLabel(
|
|
70
|
+
sampleFormat: number,
|
|
71
|
+
bitsPerSample: number
|
|
72
|
+
): string
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Combine the GeoTIFF `SampleFormat` tag with `BitsPerSample` into `'uint8'`, `'int16'`, `'float32'`, etc.
|
|
76
|
+
|
|
77
|
+
## Usage outline
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import {
|
|
81
|
+
buildDataTypeLabel,
|
|
82
|
+
clampBounds,
|
|
83
|
+
safeClamp,
|
|
84
|
+
SF_LABELS,
|
|
85
|
+
type CogInfo,
|
|
86
|
+
type GeoBounds,
|
|
87
|
+
} from '@walkthru-earth/objex-utils';
|
|
88
|
+
|
|
89
|
+
const label = buildDataTypeLabel(sampleFormat, bitsPerSample); // 'float32'
|
|
90
|
+
const safeBounds = clampBounds(bounds);
|
|
91
|
+
const nice = safeClamp(value, 0, 1, 0);
|
|
92
|
+
```
|
package/docs/errors.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Error handling
|
|
2
|
+
|
|
3
|
+
One function. Normalizes thrown values into a displayable string, with special-casing for `AbortError`.
|
|
4
|
+
|
|
5
|
+
Source: `src/lib/utils/error.ts`.
|
|
6
|
+
|
|
7
|
+
## `handleLoadError(err)`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
function handleLoadError(err: unknown): string | null
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Extract a display message from an unknown caught value.
|
|
14
|
+
|
|
15
|
+
| Input | Output | Meaning |
|
|
16
|
+
|-------|--------|---------|
|
|
17
|
+
| `DOMException` with `name === 'AbortError'` | `null` | Caller should silently return — the user cancelled. |
|
|
18
|
+
| `Error` with `name === 'AbortError'` (fetch cancel) | `null` | Same as above. |
|
|
19
|
+
| Other `Error` | `err.message` | Standard error. |
|
|
20
|
+
| Everything else | `String(err)` | Best-effort coercion. |
|
|
21
|
+
|
|
22
|
+
## Usage pattern
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { handleLoadError } from '@walkthru-earth/objex-utils';
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
data = await adapter.read(path, 0, undefined, signal);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
const msg = handleLoadError(err);
|
|
31
|
+
if (msg === null) return; // aborted — do nothing
|
|
32
|
+
errorState = msg;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# File sorting
|
|
2
|
+
|
|
3
|
+
Sort a `FileEntry[]` with directories pinned on top. Pure — no mutation of the input.
|
|
4
|
+
|
|
5
|
+
Source: `src/lib/utils/file-sort.ts`.
|
|
6
|
+
|
|
7
|
+
## Types
|
|
8
|
+
|
|
9
|
+
### `SortField`
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
type SortField = 'name' | 'size' | 'modified' | 'extension';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### `SortDirection`
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
type SortDirection = 'asc' | 'desc';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### `SortConfig`
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface SortConfig {
|
|
25
|
+
field: SortField;
|
|
26
|
+
direction: SortDirection;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Functions
|
|
31
|
+
|
|
32
|
+
### `sortFileEntries(entries, config)`
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
function sortFileEntries(entries: FileEntry[], config: SortConfig): FileEntry[]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Return a **new** array sorted according to `config`.
|
|
39
|
+
|
|
40
|
+
- Directories always sort before files regardless of direction.
|
|
41
|
+
- `name` uses locale-aware comparison.
|
|
42
|
+
- `size` and `modified` use numeric comparison.
|
|
43
|
+
- `extension` falls back to `name` when equal.
|
|
44
|
+
- Stable for equal keys.
|
|
45
|
+
|
|
46
|
+
### `toggleSortField(current, field)`
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
function toggleSortField(current: SortConfig, field: SortField): SortConfig
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
UI helper for clickable column headers:
|
|
53
|
+
|
|
54
|
+
- If `field === current.field`: flip direction.
|
|
55
|
+
- Otherwise: switch to `{ field, direction: 'asc' }`.
|
|
56
|
+
|
|
57
|
+
## Example
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { sortFileEntries, toggleSortField } from '@walkthru-earth/objex-utils';
|
|
61
|
+
|
|
62
|
+
let config = { field: 'modified', direction: 'desc' };
|
|
63
|
+
const sorted = sortFileEntries(entries, config);
|
|
64
|
+
|
|
65
|
+
// onClick a column header:
|
|
66
|
+
config = toggleSortField(config, 'size');
|
|
67
|
+
```
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# File-type registry
|
|
2
|
+
|
|
3
|
+
Central mapping between file extensions and everything objex needs to render or query them — icon, color, viewer, MIME type, DuckDB read function.
|
|
4
|
+
|
|
5
|
+
Source: `src/lib/file-icons/index.ts`.
|
|
6
|
+
|
|
7
|
+
## Types
|
|
8
|
+
|
|
9
|
+
### `FileCategory`
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
type FileCategory =
|
|
13
|
+
| 'data' | 'geo' | 'code' | 'document' | 'config'
|
|
14
|
+
| 'image' | 'video' | 'audio' | 'archive'
|
|
15
|
+
| 'database' | '3d' | 'other';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### `ViewerKind`
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
type ViewerKind =
|
|
22
|
+
| 'table' | 'image' | 'video' | 'audio' | 'markdown' | 'code'
|
|
23
|
+
| 'cog' | 'pmtiles' | 'flatgeobuf' | 'pdf' | '3d' | 'archive'
|
|
24
|
+
| 'database' | 'zarr' | 'copc' | 'notebook' | 'raw';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use this to drive viewer routing in your own UI.
|
|
28
|
+
|
|
29
|
+
### `DuckDbReadFn`
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
type DuckDbReadFn = 'read_parquet' | 'read_csv' | 'read_json' | 'ST_Read';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Which DuckDB table function should be used to ingest the file. `ST_Read` covers GDAL-backed vector formats (Shapefile, GeoPackage, FlatGeobuf, KML, GML).
|
|
36
|
+
|
|
37
|
+
### `FileTypeInfo`
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
interface FileTypeInfo {
|
|
41
|
+
icon: string; // Lucide icon name
|
|
42
|
+
color: string; // Tailwind color classes (light + dark)
|
|
43
|
+
label: string; // human-readable type label
|
|
44
|
+
category: FileCategory;
|
|
45
|
+
viewer: ViewerKind;
|
|
46
|
+
queryable: boolean;
|
|
47
|
+
duckdbReadFn: DuckDbReadFn | null;
|
|
48
|
+
mimeType: string;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Functions
|
|
53
|
+
|
|
54
|
+
### `getFileTypeInfo(extension, isDir?)`
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
function getFileTypeInfo(extension: string, isDir?: boolean): FileTypeInfo
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Return a fully populated `FileTypeInfo` for an extension.
|
|
61
|
+
|
|
62
|
+
- `extension` is matched with or without a leading dot, case-insensitive.
|
|
63
|
+
- `isDir === true` short-circuits to a directory entry (folder icon, `category: 'other'`, `viewer: 'raw'`).
|
|
64
|
+
- Unknown extensions return the `raw` entry.
|
|
65
|
+
|
|
66
|
+
### `getViewerKind(extension)`
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
function getViewerKind(extension: string): ViewerKind
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Shorthand that returns `info.viewer` only. `extension` may be a full filename or an extension.
|
|
73
|
+
|
|
74
|
+
### `getMimeType(extension)`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
function getMimeType(extension: string): string
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Return the MIME type (`application/octet-stream` fallback). Useful for `Content-Type` on uploads and `<a download>` links.
|
|
81
|
+
|
|
82
|
+
### `isQueryable(extension)`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
function isQueryable(extension: string): boolean
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`true` when the format can be queried with DuckDB (Parquet, CSV, TSV, JSONL, NDJSON, Shapefile, GeoPackage, FlatGeobuf, KML, GML, etc.).
|
|
89
|
+
|
|
90
|
+
### `getDuckDbReadFn(pathOrExt)`
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
function getDuckDbReadFn(pathOrExt: string): string
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Return the DuckDB table-function name (`'read_parquet'`, `'read_csv'`, `'read_json'`, `'ST_Read'`). Falls back to `'read_parquet'` when unknown.
|
|
97
|
+
|
|
98
|
+
Accepts either a file path or a bare extension.
|
|
99
|
+
|
|
100
|
+
### `isCloudNativeFormat(pathOrExt)`
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
function isCloudNativeFormat(pathOrExt: string): boolean
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`true` for formats DuckDB can query directly over HTTP range requests without buffering the whole file (`.parquet`, `.geoparquet`, `.gpq`, `.gparquet`, `.ducklake`).
|
|
107
|
+
|
|
108
|
+
### `buildDuckDbSource(pathOrExt, url)`
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
function buildDuckDbSource(pathOrExt: string, url: string): string
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Return a ready-to-paste FROM-clause expression. Key behavior:
|
|
115
|
+
|
|
116
|
+
| Ext | Output |
|
|
117
|
+
|-----|--------|
|
|
118
|
+
| `.parquet` / `.geoparquet` | `read_parquet('<url>')` |
|
|
119
|
+
| `.csv` / `.tsv` | `read_csv('<url>')` |
|
|
120
|
+
| `.jsonl` / `.ndjson` | `read_json('<url>')` |
|
|
121
|
+
| `.json` | Unnested expression that flattens GeoJSON `features[*]` into rows with property columns + a `geometry` column |
|
|
122
|
+
| `.shp`, `.gpkg`, `.fgb`, `.kml`, `.gml` | `ST_Read('<url>')` |
|
|
123
|
+
| Unknown | `read_parquet('<url>')` fallback |
|
|
124
|
+
|
|
125
|
+
## Example
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import {
|
|
129
|
+
getFileTypeInfo,
|
|
130
|
+
isQueryable,
|
|
131
|
+
buildDuckDbSource,
|
|
132
|
+
} from '@walkthru-earth/objex-utils';
|
|
133
|
+
|
|
134
|
+
const info = getFileTypeInfo('streets.fgb');
|
|
135
|
+
// { viewer: 'flatgeobuf', category: 'geo', queryable: true, duckdbReadFn: 'ST_Read', ... }
|
|
136
|
+
|
|
137
|
+
if (isQueryable('streets.fgb')) {
|
|
138
|
+
const sql = `SELECT COUNT(*) FROM ${buildDuckDbSource('streets.fgb', url)}`;
|
|
139
|
+
// SELECT COUNT(*) FROM ST_Read('https://.../streets.fgb')
|
|
140
|
+
}
|
|
141
|
+
```
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Formatting, column types, hex, export
|
|
2
|
+
|
|
3
|
+
Display formatters, column-type classification, hex dump, and data serialization. All sync, pure, no browser APIs required unless noted.
|
|
4
|
+
|
|
5
|
+
Sources:
|
|
6
|
+
|
|
7
|
+
- `src/lib/utils/format.ts`
|
|
8
|
+
- `src/lib/utils/column-types.ts`
|
|
9
|
+
- `src/lib/utils/hex.ts`
|
|
10
|
+
- `src/lib/utils/export.ts`
|
|
11
|
+
|
|
12
|
+
## Formatters
|
|
13
|
+
|
|
14
|
+
### `formatFileSize(bytes)`
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
function formatFileSize(bytes: number): string
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
1024-based human-readable byte count. Integer for raw bytes (`'512 B'`), one decimal for everything else (`'1.5 MB'`). Returns `'0 B'` for `0`, `'-'` for negative.
|
|
21
|
+
|
|
22
|
+
### `formatDate(timestamp)`
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
function formatDate(timestamp: number): string
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Format a unix timestamp **in milliseconds** as a human-readable date string.
|
|
29
|
+
|
|
30
|
+
- Recent timestamps render relative: `'just now'`, `'5m ago'`, `'2h ago'`, `'3d ago'`.
|
|
31
|
+
- Older render as `'YYYY-MM-DD'`.
|
|
32
|
+
- Missing / invalid → `'--'`.
|
|
33
|
+
|
|
34
|
+
### `formatValue(value)`
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
function formatValue(value: unknown): string
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Format any value for display in tables, panels, or exports.
|
|
41
|
+
|
|
42
|
+
| Input | Output |
|
|
43
|
+
|-------|--------|
|
|
44
|
+
| `null` / `undefined` | `''` |
|
|
45
|
+
| `bigint` | `value.toString()` |
|
|
46
|
+
| `Date` | `value.toISOString()` |
|
|
47
|
+
| Object (incl. arrays) | `JSON.stringify(value, jsonReplacerBigInt)` |
|
|
48
|
+
| Everything else | `String(value)` |
|
|
49
|
+
|
|
50
|
+
### `getFileExtension(filename)`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
function getFileExtension(filename: string): string
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Return the extension **including** the leading dot (`'data.parquet' → '.parquet'`). Empty string if no extension. Case-preserving.
|
|
57
|
+
|
|
58
|
+
### `jsonReplacerBigInt(_key, value)`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
function jsonReplacerBigInt(_key: string, value: unknown): unknown
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`JSON.stringify` replacer that coerces `BigInt` to decimal strings so DuckDB `BIGINT`s don't explode serialization.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
JSON.stringify(row, jsonReplacerBigInt);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Column-type classification
|
|
71
|
+
|
|
72
|
+
### `TypeCategory`
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
type TypeCategory =
|
|
76
|
+
| 'number' | 'string' | 'date' | 'boolean'
|
|
77
|
+
| 'geo' | 'binary' | 'json' | 'other';
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `classifyType(duckdbType)`
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
function classifyType(duckdbType: string): TypeCategory
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Classify a DuckDB/Arrow type string. Handles parameterized types (`DECIMAL(18,3)`, `VARCHAR(100)`), compound types (`STRUCT`, `MAP`, `LIST`), and fuzzy keyword matching as a fallback.
|
|
87
|
+
|
|
88
|
+
| Example inputs | Result |
|
|
89
|
+
|----------------|--------|
|
|
90
|
+
| `BIGINT`, `DOUBLE`, `DECIMAL(18,3)` | `'number'` |
|
|
91
|
+
| `VARCHAR`, `STRING`, `UTF8` | `'string'` |
|
|
92
|
+
| `TIMESTAMP`, `DATE`, `TIME` | `'date'` |
|
|
93
|
+
| `BOOLEAN` | `'boolean'` |
|
|
94
|
+
| `GEOMETRY`, `GEOMETRY('EPSG:27700')`, `POINT`, `WKB_BLOB` | `'geo'` |
|
|
95
|
+
| `BLOB`, `BINARY`, `VARBINARY` | `'binary'` |
|
|
96
|
+
| `STRUCT<...>`, `MAP<...>`, `LIST<...>`, `JSON` | `'json'` |
|
|
97
|
+
| Otherwise | `'other'` |
|
|
98
|
+
|
|
99
|
+
### `typeColor(category)`
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
function typeColor(category: TypeCategory): string
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Tailwind text-color class (e.g. `'text-blue-500'`) for dots / badges.
|
|
106
|
+
|
|
107
|
+
### `typeBadgeClass(category)`
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
function typeBadgeClass(category: TypeCategory): string
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Tailwind classes for a pill-style badge (background + text + border).
|
|
114
|
+
|
|
115
|
+
### `typeLabel(category)`
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
function typeLabel(category: TypeCategory): string
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Short symbol (`'#'` for number, `'{}'` for json, `'geo'` for geo, etc.) for compact badges.
|
|
122
|
+
|
|
123
|
+
## Hex dump
|
|
124
|
+
|
|
125
|
+
### `HexRow`
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
interface HexRow {
|
|
129
|
+
offset: string; // hex offset, zero-padded (e.g. '00000000')
|
|
130
|
+
hex: string[]; // per-byte hex (e.g. '48', '65')
|
|
131
|
+
ascii: string; // printable ASCII, '.' for non-printable
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `generateHexDump(data, bytesPerRow?)`
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
function generateHexDump(data: Uint8Array, bytesPerRow?: number): HexRow[]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Build a structured hex dump. `bytesPerRow` defaults to `16`. Pure — no DOM.
|
|
142
|
+
|
|
143
|
+
Rendering is left to the caller; `HexRow` maps directly to `<tr>` / CSV / JSON.
|
|
144
|
+
|
|
145
|
+
## Data export
|
|
146
|
+
|
|
147
|
+
### `escapeCsvField(value)`
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
function escapeCsvField(value: string): string
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Escape a single CSV field per RFC 4180: wraps in double quotes and doubles any embedded quote **only when** the value contains `,`, `"`, `\n`, or `\r`. Otherwise returns the value unchanged.
|
|
154
|
+
|
|
155
|
+
### `serializeToCsv(columns, rows)`
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
function serializeToCsv(
|
|
159
|
+
columns: string[],
|
|
160
|
+
rows: Record<string, unknown>[]
|
|
161
|
+
): string
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Pure serializer. Uses `formatValue` internally, so `bigint` / `Date` / objects round-trip correctly. Row terminator is `'\n'`.
|
|
165
|
+
|
|
166
|
+
### `serializeToJson(columns, rows)`
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
function serializeToJson(
|
|
170
|
+
columns: string[],
|
|
171
|
+
rows: Record<string, unknown>[]
|
|
172
|
+
): string
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Pure. Returns pretty-printed JSON (`2`-space indent). `Date` → ISO, `BigInt` → decimal string, `null` preserved.
|
|
176
|
+
|
|
177
|
+
### `exportToCsv(columns, rows, filename)` / `exportToJson(columns, rows, filename)`
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
function exportToCsv(
|
|
181
|
+
columns: string[],
|
|
182
|
+
rows: Record<string, unknown>[],
|
|
183
|
+
filename: string
|
|
184
|
+
): void;
|
|
185
|
+
function exportToJson(
|
|
186
|
+
columns: string[],
|
|
187
|
+
rows: Record<string, unknown>[],
|
|
188
|
+
filename: string
|
|
189
|
+
): void;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Browser-only wrappers that build the blob and trigger a download via a hidden `<a>`. Throw `ReferenceError` in non-DOM environments — use the `serializeToCsv` / `serializeToJson` variants server-side.
|