@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.
@@ -0,0 +1,198 @@
1
+ # Geometry
2
+
3
+ WKB parsing, geometry-column detection, and GeoArrow table construction. Zero-copy where possible.
4
+
5
+ Source: `src/lib/utils/wkb.ts`, `src/lib/utils/geoarrow.ts`.
6
+
7
+ ## Types
8
+
9
+ ### `GeoType`
10
+
11
+ ```ts
12
+ type GeoType =
13
+ | 'Point'
14
+ | 'LineString'
15
+ | 'Polygon'
16
+ | 'MultiPoint'
17
+ | 'MultiLineString'
18
+ | 'MultiPolygon'
19
+ | 'Unknown';
20
+ ```
21
+
22
+ `'Unknown'` is returned for unsupported WKB types (GeometryCollections, TINs, triangles, etc.).
23
+
24
+ ### `ParsedGeometry`
25
+
26
+ ```ts
27
+ interface ParsedGeometry {
28
+ type: GeoType;
29
+ coordinates: number[] | number[][] | number[][][] | number[][][][];
30
+ }
31
+ ```
32
+
33
+ Coordinate nesting follows GeoJSON conventions.
34
+
35
+ | Type | Shape |
36
+ |------|-------|
37
+ | `Point` | `[x, y]` |
38
+ | `MultiPoint` / `LineString` | `[[x, y], ...]` |
39
+ | `MultiLineString` / `Polygon` | `[[[x, y], ...], ...]` |
40
+ | `MultiPolygon` | `[[[[x, y], ...], ...], ...]` |
41
+
42
+ ### `GeoArrowGeomType`
43
+
44
+ ```ts
45
+ type GeoArrowGeomType =
46
+ | 'point'
47
+ | 'linestring'
48
+ | 'polygon'
49
+ | 'multipoint'
50
+ | 'multilinestring'
51
+ | 'multipolygon';
52
+ ```
53
+
54
+ Lowercase normalized form used by the GeoArrow builder and `@geoarrow/deck.gl-layers`.
55
+
56
+ ### `GeoArrowResult`
57
+
58
+ ```ts
59
+ interface GeoArrowResult {
60
+ table: Table; // apache-arrow Table
61
+ geometryType: GeoArrowGeomType;
62
+ bounds: [number, number, number, number]; // [minX, minY, maxX, maxY]
63
+ sourceIndices: number[]; // table row i → original wkbArrays[sourceIndices[i]]
64
+ }
65
+ ```
66
+
67
+ `sourceIndices` lets callers map picked rows back to the original row order when mixed geometry types force a split.
68
+
69
+ ## Functions
70
+
71
+ ### `toBinary(value)`
72
+
73
+ ```ts
74
+ function toBinary(value: unknown): Uint8Array | null
75
+ ```
76
+
77
+ Normalize an arbitrary "possibly-binary" value to a `Uint8Array`.
78
+
79
+ | Input | Handling |
80
+ |-------|----------|
81
+ | `Uint8Array` | Returned as-is |
82
+ | `ArrayBuffer` | Wrapped in `new Uint8Array(buf)` |
83
+ | `number[]` | `new Uint8Array(arr)` |
84
+ | Hex string (even length, `[0-9a-fA-F]`) | Decoded to bytes |
85
+ | DuckDB `toJSON()` blob object `{0: b0, 1: b1, ...}` | Reassembled into bytes |
86
+ | Anything else | `null` |
87
+
88
+ Returns **`null`** rather than throwing on unrecognized input, so callers can fall through.
89
+
90
+ ### `parseWKB(data)`
91
+
92
+ ```ts
93
+ function parseWKB(data: Uint8Array): ParsedGeometry | null
94
+ ```
95
+
96
+ Parse a WKB byte blob.
97
+
98
+ - Supports standard WKB, ISO WKB with Z/M flags, and EWKB with SRID prefix (PostGIS). Z/M ordinates are **dropped** — only X/Y is returned.
99
+ - Supports Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon.
100
+ - Returns **`null`** for truncated buffers, invalid byte-order flags, or malformed geometry.
101
+ - GeometryCollections (WKB type 7) return `{ type: 'Unknown', coordinates: [] }`.
102
+
103
+ ### `findGeoColumn(schema)`
104
+
105
+ ```ts
106
+ function findGeoColumn(
107
+ schema: { name: string; type: string }[]
108
+ ): string | null
109
+ ```
110
+
111
+ Schema-only heuristic for locating the geometry column. Checks in priority order:
112
+
113
+ 1. **Type** contains a geometry keyword (`GEOMETRY`, `POINT`, `WKB`, ...) — match wins immediately.
114
+ 2. Exact well-known **name** (`geometry`, `geom`, `the_geom`, ...) **with** a binary-ish type (`BLOB`, `BINARY`, `VARBINARY`, `BYTES`).
115
+ 3. Exact well-known **name**, any type.
116
+ 4. Name contains a geo hint (`geom`, `geo_`, `wkb`, `wkt`, `shape`, `spatial`) with binary-ish type.
117
+ 5. Name contains geo hint, any type.
118
+
119
+ Returns the first matching `name`, or `null` if no heuristic hits. Use `findGeoColumnFromRows` as a fallback when the schema is not informative.
120
+
121
+ ### `findGeoColumnFromRows(rows, schema)`
122
+
123
+ ```ts
124
+ function findGeoColumnFromRows(
125
+ rows: Record<string, unknown>[],
126
+ schema: { name: string; type: string }[]
127
+ ): string | null
128
+ ```
129
+
130
+ Row-based probe. Samples the first row and classifies values:
131
+
132
+ - Binary-typed columns: tests for WKB magic bytes (endian flag 0x00/0x01 + valid geometry type).
133
+ - Other columns: probes hex-encoded WKB, WKT strings starting with `POINT(`, `LINESTRING(`, etc., and GeoJSON geometry objects (`{type: 'Point', coordinates: [...]}`).
134
+
135
+ Returns the first column whose value looks geometry-shaped, or `null`.
136
+
137
+ ### `normalizeGeomType(raw)`
138
+
139
+ ```ts
140
+ function normalizeGeomType(raw: string): GeoArrowGeomType
141
+ ```
142
+
143
+ Map a DuckDB `ST_GeometryType()` result (`'POINT'`, `'ST_Polygon'`, etc., case-insensitive, optional `ST_` prefix) to a `GeoArrowGeomType`. Unknown input falls back to `'polygon'`.
144
+
145
+ ### `buildGeoArrowTables(wkbArrays, attributes, knownGeomType?)`
146
+
147
+ ```ts
148
+ function buildGeoArrowTables(
149
+ wkbArrays: Uint8Array[],
150
+ attributes: Map<string, { values: any[]; type: string }>,
151
+ knownGeomType?: GeoArrowGeomType
152
+ ): GeoArrowResult[]
153
+ ```
154
+
155
+ Build one or more Arrow `Table` objects keyed by geometry type, ready for `@geoarrow/deck.gl-layers`.
156
+
157
+ **Parameters**
158
+
159
+ | Name | Type | Meaning |
160
+ |------|------|---------|
161
+ | `wkbArrays` | `Uint8Array[]` | Per-row WKB binary. Entries may be empty / invalid — they are skipped. |
162
+ | `attributes` | `Map<name, { values, type }>` | Non-geometry columns. `values.length` must equal `wkbArrays.length`. `type` is a DuckDB/Arrow type string used for Arrow dtype inference (numeric → Float64, bool → Bool, everything else → Utf8). |
163
+ | `knownGeomType` | optional | If provided (e.g. from GeoParquet metadata), classification is skipped — all WKBs are assumed to share this type, and one `GeoArrowResult` is returned. |
164
+
165
+ **Behavior**
166
+
167
+ - **Zero-copy geometry ingest.** A 5-byte peek classifies each WKB; coordinates are read directly into pre-allocated `Float64Array` backings with no intermediate JS objects.
168
+ - **Mixed-type splitting.** If `knownGeomType` is not provided and the rows span multiple types (e.g. `Polygon` and `MultiPolygon`), rows are partitioned and one `GeoArrowResult` is emitted per non-empty group.
169
+ - **Bounds.** Each result carries a computed `[minX, minY, maxX, maxY]`. When splitting, each group sees its own tight bounds.
170
+ - **Attribute propagation.** Attribute values follow the split via `sourceIndices`.
171
+ - **Empty / unknown WKBs** are silently dropped.
172
+
173
+ **Returns** `GeoArrowResult[]` — empty array if no rows have parseable geometries.
174
+
175
+ **Peer dependencies**
176
+
177
+ - `apache-arrow` (used to construct the returned `Table`).
178
+
179
+ **Example**
180
+
181
+ ```ts
182
+ import { buildGeoArrowTables, normalizeGeomType } from '@walkthru-earth/objex-utils';
183
+
184
+ const wkbArrays: Uint8Array[] = rows.map(r => r.geometry);
185
+ const attributes = new Map([
186
+ ['id', { values: rows.map(r => r.id), type: 'BIGINT' }],
187
+ ['name', { values: rows.map(r => r.name), type: 'VARCHAR' }]
188
+ ]);
189
+
190
+ // When you know the type from GeoParquet metadata:
191
+ const [result] = buildGeoArrowTables(wkbArrays, attributes, normalizeGeomType('POLYGON'));
192
+
193
+ // When you do not:
194
+ const results = buildGeoArrowTables(wkbArrays, attributes);
195
+ for (const r of results) {
196
+ console.log(r.geometryType, r.table.numRows, r.bounds);
197
+ }
198
+ ```
@@ -0,0 +1,51 @@
1
+ # localStorage helpers
2
+
3
+ SSR-safe JSON persistence on top of `window.localStorage`.
4
+
5
+ Source: `src/lib/utils/local-storage.ts`.
6
+
7
+ ## Functions
8
+
9
+ ### `loadFromStorage<T>(key, defaultValue)`
10
+
11
+ ```ts
12
+ function loadFromStorage<T>(key: string, defaultValue: T): T
13
+ ```
14
+
15
+ Load a JSON value from localStorage. Returns `defaultValue` when:
16
+
17
+ - Running in SSR (`typeof window === 'undefined'`).
18
+ - The key is not present.
19
+ - `JSON.parse` throws (stored value corrupted).
20
+
21
+ Never throws.
22
+
23
+ ### `persistToStorage(key, value)`
24
+
25
+ ```ts
26
+ function persistToStorage(key: string, value: unknown): void
27
+ ```
28
+
29
+ Write a JSON-serializable value to localStorage. No-ops silently when:
30
+
31
+ - Running in SSR.
32
+ - `localStorage.setItem` throws (quota exceeded, private-browsing restrictions, Safari storage partitioning).
33
+
34
+ Pair with the [`STORAGE_KEYS`](./types-constants.md#storage_keys) constants to keep key strings consistent across the app.
35
+
36
+ ## Example
37
+
38
+ ```ts
39
+ import { loadFromStorage, persistToStorage, STORAGE_KEYS } from '@walkthru-earth/objex-utils';
40
+
41
+ interface Settings {
42
+ theme: 'light' | 'dark' | 'system';
43
+ locale: 'en' | 'ar';
44
+ }
45
+
46
+ const defaults: Settings = { theme: 'system', locale: 'en' };
47
+ let settings = loadFromStorage<Settings>(STORAGE_KEYS.SETTINGS, defaults);
48
+
49
+ // later…
50
+ persistToStorage(STORAGE_KEYS.SETTINGS, settings);
51
+ ```
@@ -0,0 +1,109 @@
1
+ # Markdown + SQL parsing
2
+
3
+ Parse markdown documents with YAML frontmatter and Evidence-style SQL code blocks, run interpolation, and stub blocks for server-side rendering.
4
+
5
+ Source: `src/lib/utils/markdown-sql.ts`.
6
+
7
+ ## Peer dependency
8
+
9
+ - `yaml >= 2` — **dynamically imported** only when frontmatter is detected. If `yaml` is not installed and the document has frontmatter, parsing silently returns `frontmatter: {}` and continues. Consumers who never call `parseMarkdownDocument` do not need `yaml` at all.
10
+
11
+ ## Types
12
+
13
+ ### `SqlBlock`
14
+
15
+ ```ts
16
+ interface SqlBlock {
17
+ name: string; // block identifier from ```sql <name>
18
+ sql: string; // raw SQL body
19
+ startLine: number; // 0-indexed line index of the opening ``` fence
20
+ endLine: number; // 0-indexed line index of the closing ``` fence
21
+ }
22
+ ```
23
+
24
+ ### `ParsedMarkdownDocument`
25
+
26
+ ```ts
27
+ interface ParsedMarkdownDocument {
28
+ frontmatter: Record<string, any>;
29
+ content: string; // markdown with frontmatter stripped
30
+ sqlBlocks: SqlBlock[];
31
+ }
32
+ ```
33
+
34
+ ## Functions
35
+
36
+ ### `parseMarkdownDocument(markdown)`
37
+
38
+ ```ts
39
+ async function parseMarkdownDocument(
40
+ markdown: string
41
+ ): Promise<ParsedMarkdownDocument>
42
+ ```
43
+
44
+ **Async** (since v1.2). Parses:
45
+
46
+ - YAML frontmatter delimited by `---\n ... \n---\n` at the top of the file.
47
+ - SQL blocks of the form:
48
+
49
+ ```markdown
50
+ ```sql some_query_name
51
+ SELECT * FROM table
52
+ ```
53
+ ```
54
+
55
+ The identifier must match `/^\w[\w-]*$/`. Content is captured verbatim until the matching closing fence.
56
+
57
+ **Behavior**
58
+
59
+ - Missing or malformed frontmatter → `frontmatter = {}`.
60
+ - Missing `yaml` peer dep → `frontmatter = {}` (silent).
61
+ - No SQL blocks → `sqlBlocks = []`.
62
+
63
+ ### `interpolateTemplates(text, queryResults)`
64
+
65
+ ```ts
66
+ function interpolateTemplates(
67
+ text: string,
68
+ queryResults: Map<string, Record<string, any>[]>
69
+ ): string
70
+ ```
71
+
72
+ Replace references of the form `{queryName.rows[N].columnName}` in `text` with the corresponding value from `queryResults`.
73
+
74
+ - Unknown query name → leave untouched.
75
+ - Missing row or column → leave untouched.
76
+ - Non-string values are coerced via `String(value)`.
77
+
78
+ ### `markSqlBlocks(content)`
79
+
80
+ ```ts
81
+ function markSqlBlocks(content: string): string
82
+ ```
83
+
84
+ Replace every `` ```sql <name> ... ``` `` block in `content` with `<div data-sql-block="<name>"></div>`. Useful when streaming `content` through a markdown renderer — the caller can later hydrate each placeholder with the actual query result.
85
+
86
+ ## Pattern
87
+
88
+ ```ts
89
+ import {
90
+ parseMarkdownDocument,
91
+ interpolateTemplates,
92
+ markSqlBlocks,
93
+ } from '@walkthru-earth/objex-utils';
94
+
95
+ const parsed = await parseMarkdownDocument(rawMarkdown);
96
+
97
+ if (parsed.sqlBlocks.length) {
98
+ const results = new Map<string, Record<string, any>[]>();
99
+ await Promise.all(
100
+ parsed.sqlBlocks.map(async (b) => {
101
+ results.set(b.name, await myEngine.query(b.sql));
102
+ })
103
+ );
104
+
105
+ const interpolated = interpolateTemplates(parsed.content, results);
106
+ const withPlaceholders = markSqlBlocks(interpolated);
107
+ // hand withPlaceholders to your markdown renderer
108
+ }
109
+ ```
@@ -0,0 +1,133 @@
1
+ # Parquet metadata
2
+
3
+ Lightweight GeoParquet-aware metadata reader for remote Parquet files. Uses [`hyparquet`](https://github.com/hyparam/hyparquet) via HTTP range requests (~512 KB) so you can inspect schemas and geo metadata **before** DuckDB-WASM finishes booting.
4
+
5
+ Source: `src/lib/utils/parquet-metadata.ts`.
6
+
7
+ ## Peer dependencies
8
+
9
+ - `hyparquet >= 1.25`
10
+ - `hyparquet-compressors >= 1.1` (SNAPPY, ZSTD, GZIP, LZ4, BROTLI)
11
+
12
+ ## Types
13
+
14
+ ### `GeoColumnMeta`
15
+
16
+ ```ts
17
+ interface GeoColumnMeta {
18
+ encoding: string; // e.g. 'WKB'
19
+ geometryTypes: string[]; // e.g. ['Polygon', 'MultiPolygon']
20
+ crs: any | null; // raw ProjJSON or EPSG identifier
21
+ bbox?: number[]; // [minX, minY, maxX, maxY] (or with Z: 6 values)
22
+ }
23
+ ```
24
+
25
+ ### `GeoParquetMeta`
26
+
27
+ ```ts
28
+ interface GeoParquetMeta {
29
+ primaryColumn: string;
30
+ columns: Record<string, GeoColumnMeta>;
31
+ }
32
+ ```
33
+
34
+ ### `ParquetFileMetadata`
35
+
36
+ ```ts
37
+ interface ParquetFileMetadata {
38
+ rowCount: number;
39
+ schema: { name: string; type: string }[];
40
+ geo: GeoParquetMeta | null; // null for non-geo Parquet
41
+ legacyGeoParquet: boolean; // true for pre-1.0 (schema_version without "version" field)
42
+ createdBy: string | null;
43
+ numRowGroups: number;
44
+ compression: string | null; // e.g. 'SNAPPY', 'ZSTD'
45
+ }
46
+ ```
47
+
48
+ ## Functions
49
+
50
+ ### `readParquetMetadata(url)`
51
+
52
+ ```ts
53
+ async function readParquetMetadata(url: string): Promise<ParquetFileMetadata>
54
+ ```
55
+
56
+ Read the Parquet footer from a remote URL via range requests.
57
+
58
+ **Parameters**
59
+
60
+ | Name | Type | Meaning |
61
+ |------|------|---------|
62
+ | `url` | `string` | Full HTTPS URL. Must be CORS-accessible. `s3://` / `gs://` URIs must be resolved first (see [`resolveCloudUrl`](./storage.md#resolvecloudurl)). |
63
+
64
+ **Returns** `Promise<ParquetFileMetadata>`.
65
+
66
+ **Throws** a native error if the URL is not reachable, CORS is blocked, or the footer is malformed.
67
+
68
+ **Notes**
69
+
70
+ - The `geo` field contains the parsed `"geo"` key from Parquet file-level KV metadata. For legacy files (`schema_version` but no `version`), it is still parsed; `legacyGeoParquet` is set so callers can apply fallbacks.
71
+ - `compression` comes from the first row group's first column and is reported capitalized.
72
+
73
+ ### `extractEpsgFromGeoMeta(geo)`
74
+
75
+ ```ts
76
+ function extractEpsgFromGeoMeta(geo: GeoParquetMeta): string | null
77
+ ```
78
+
79
+ Extract an EPSG authority code from a GeoParquet CRS block. Returns `null` for WGS84/CRS84 (no reprojection needed) or when no EPSG identifier is embedded.
80
+
81
+ **Return examples**
82
+
83
+ - `'EPSG:27700'` (British National Grid)
84
+ - `'EPSG:3857'` (Web Mercator)
85
+ - `null` (WGS84 or CRS absent)
86
+
87
+ ### `extractGeometryTypes(geo)`
88
+
89
+ ```ts
90
+ function extractGeometryTypes(
91
+ geo: GeoParquetMeta
92
+ ): GeoArrowGeomType[]
93
+ ```
94
+
95
+ Pull the `geometry_types` array from the primary column's metadata and normalize it into [`GeoArrowGeomType`](./geometry.md#geoarrowgeomtype). Useful to skip per-row `ST_GeometryType()` calls.
96
+
97
+ ### `extractBounds(geo)`
98
+
99
+ ```ts
100
+ function extractBounds(
101
+ geo: GeoParquetMeta
102
+ ): [number, number, number, number] | null
103
+ ```
104
+
105
+ Extract the `bbox` from the primary column. Returns `null` when absent. If the bbox has Z (`[minX, minY, minZ, maxX, maxY, maxZ]`), only the XY extent is returned.
106
+
107
+ ## End-to-end example
108
+
109
+ ```ts
110
+ import {
111
+ readParquetMetadata,
112
+ extractEpsgFromGeoMeta,
113
+ extractGeometryTypes,
114
+ extractBounds,
115
+ } from '@walkthru-earth/objex-utils';
116
+
117
+ const meta = await readParquetMetadata(
118
+ 'https://example.com/data.parquet'
119
+ );
120
+
121
+ console.log({
122
+ rows: meta.rowCount,
123
+ compression: meta.compression,
124
+ schema: meta.schema,
125
+ });
126
+
127
+ if (meta.geo) {
128
+ const crs = extractEpsgFromGeoMeta(meta.geo); // null means WGS84
129
+ const types = extractGeometryTypes(meta.geo); // ['polygon']
130
+ const bbox = extractBounds(meta.geo); // [minX, minY, maxX, maxY] | null
131
+ console.log({ crs, types, bbox, legacy: meta.legacyGeoParquet });
132
+ }
133
+ ```
@@ -0,0 +1,140 @@
1
+ # Query engine
2
+
3
+ Interfaces only. `@walkthru-earth/objex-utils` does not ship a DuckDB-WASM implementation; the objex app provides one (`src/lib/query/wasm.ts`). Use `QueryEngine` as the shape your own engine must implement, or that downstream consumers of your library can depend on.
4
+
5
+ Source: `src/lib/query/engine.ts`.
6
+
7
+ ## Types
8
+
9
+ ### `QueryResult`
10
+
11
+ ```ts
12
+ interface QueryResult {
13
+ columns: string[];
14
+ types: string[];
15
+ rowCount: number;
16
+ rows: Record<string, any>[]; // already materialized
17
+ }
18
+ ```
19
+
20
+ Pre-parsed rows avoid the Arrow version-mismatch surface in DuckDB-WASM (ships Arrow v17 internally while the project may use Arrow v21).
21
+
22
+ ### `MapQueryResult`
23
+
24
+ ```ts
25
+ interface MapQueryResult {
26
+ wkbArrays: Uint8Array[]; // geometry column as raw WKB
27
+ geometryType: string; // e.g. 'POLYGON'
28
+ attributes: Map<string, { values: any[]; type: string }>; // non-geometry columns, columnar
29
+ rowCount: number;
30
+ }
31
+ ```
32
+
33
+ Raw columnar shape for map rendering — feed straight into [`buildGeoArrowTables`](./geometry.md#buildgeoarrowtables).
34
+
35
+ ### `SchemaField`
36
+
37
+ ```ts
38
+ interface SchemaField {
39
+ name: string;
40
+ type: string; // DuckDB type string, e.g. 'VARCHAR', 'GEOMETRY(EPSG:27700)'
41
+ nullable: boolean;
42
+ }
43
+ ```
44
+
45
+ ### `QuerySource`
46
+
47
+ ```ts
48
+ interface QuerySource {
49
+ ref: string; // FROM-clause expression, e.g. read_parquet('...url...') or "db"."schema"."table"
50
+ filePath?: string; // optional shortcut for Parquet file metadata queries
51
+ }
52
+ ```
53
+
54
+ ### Cancellation
55
+
56
+ ```ts
57
+ interface QueryHandle {
58
+ result: Promise<QueryResult>;
59
+ cancel(): Promise<boolean>; // true if cancelled, false if already completed
60
+ }
61
+
62
+ interface MapQueryHandle {
63
+ result: Promise<MapQueryResult>;
64
+ cancel(): Promise<boolean>;
65
+ }
66
+
67
+ class QueryCancelledError extends Error {
68
+ name: 'QueryCancelledError';
69
+ }
70
+ ```
71
+
72
+ The cancellable variants are how `objex` drives long-running queries — `cancel()` invokes DuckDB's `conn.send()` cancel path so the single worker isn't held hostage by an abandoned tab.
73
+
74
+ ## `QueryEngine` interface
75
+
76
+ ```ts
77
+ interface QueryEngine {
78
+ query(connId: string, sql: string): Promise<QueryResult>;
79
+
80
+ queryForMap(
81
+ connId: string,
82
+ sql: string,
83
+ geomCol: string,
84
+ geomColType: string,
85
+ sourceCrs?: string | null
86
+ ): Promise<MapQueryResult>;
87
+
88
+ getSchema(connId: string, source: QuerySource): Promise<SchemaField[]>;
89
+ getRowCount(connId: string, source: QuerySource): Promise<number>;
90
+ detectCrs(
91
+ connId: string,
92
+ source: QuerySource,
93
+ geomCol: string
94
+ ): Promise<string | null>;
95
+
96
+ getSchemaAndCrs?(
97
+ connId: string,
98
+ source: QuerySource,
99
+ findGeoCol: (schema: SchemaField[]) => string | null
100
+ ): Promise<{ schema: SchemaField[]; geomCol: string | null; crs: string | null }>;
101
+
102
+ queryCancellable?(connId: string, sql: string): QueryHandle;
103
+ queryForMapCancellable?(
104
+ connId: string,
105
+ sql: string,
106
+ geomCol: string,
107
+ geomColType: string,
108
+ sourceCrs?: string | null
109
+ ): MapQueryHandle;
110
+
111
+ forceCancel?(): Promise<void>;
112
+ registerFileBuffer?(name: string, buffer: Uint8Array): Promise<void>;
113
+ dropFile?(name: string): Promise<void>;
114
+
115
+ releaseMemory(): Promise<void>;
116
+ dispose(): Promise<void>;
117
+ }
118
+ ```
119
+
120
+ ### Methods at a glance
121
+
122
+ | Method | Required? | Notes |
123
+ |--------|-----------|-------|
124
+ | `query` | yes | Fire-and-forget query returning pre-parsed rows. |
125
+ | `queryForMap` | yes | Geometry-aware query. `geomColType` lets the engine decide whether to wrap in `ST_AsWKB` / `ST_Transform`. `sourceCrs` (e.g. `'EPSG:27700'`) is only used when the geometry column does not have a parameterized GEOMETRY type; pass `null` for WGS84. |
126
+ | `getSchema` / `getRowCount` | yes | Metadata helpers. `QuerySource.ref` is the FROM expression — works for both files and catalog tables. |
127
+ | `detectCrs` | yes | Returns e.g. `'EPSG:27700'` or `null` (WGS84 / unknown). |
128
+ | `getSchemaAndCrs` | optional | Single round-trip combining the three above. `findGeoCol` is injected so the engine can inspect the schema without owning the heuristic. |
129
+ | `queryCancellable` / `queryForMapCancellable` | optional | Prefer these in UIs. Falls back to non-cancellable when absent. |
130
+ | `forceCancel` | optional | Kill any in-flight query across all handles. |
131
+ | `registerFileBuffer` / `dropFile` | optional | Register an in-memory file in DuckDB's VFS (used for ATTACH'd catalogs, drag-and-drop). |
132
+ | `releaseMemory` | yes | Trim DuckDB buffer pools (e.g. after closing large tabs). |
133
+ | `dispose` | yes | Tear down the connection fully. |
134
+
135
+ ### Implementation checklist
136
+
137
+ - Serialize rows with BigInt-safe JSON (see [`jsonReplacerBigInt`](./formatting.md#jsonreplacerbigint)).
138
+ - Wrap geometry selects with `ST_AsWKB(...)` before Arrow export — DuckDB-WASM cannot Arrow-export `GEOMETRY` yet ([duckdb/duckdb-wasm#2187](https://github.com/duckdb/duckdb-wasm/issues/2187)).
139
+ - Return `rowCount` even when the result is streamed; consumers display progress.
140
+ - Surface cancellation via `QueryCancelledError` so UIs can distinguish "user aborted" from real failures.