@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/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.