@mailwoman/resolver-wof-wasm 0.1.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/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @mailwoman/resolver-wof-wasm
2
+
3
+ Browser-side WOF resolver backed by [`@sqlite.org/sqlite-wasm`](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm). Drop-in `PlaceLookup` implementation for the browser-side mailwoman demo (Phase B of the demo plan — see [sister-software/mailwoman#98](https://github.com/sister-software/mailwoman/issues/98)).
4
+
5
+ Pair with [`@mailwoman/resolver-wof-sqlite`](https://www.npmjs.com/package/@mailwoman/resolver-wof-sqlite)'s `mailwoman-wof-build-slim` CLI to produce a ~35 MB slim distribution suitable for static-asset deployment, then load it from a URL at runtime.
6
+
7
+ ## Status
8
+
9
+ **v0.1.0 — scaffold + minimal `findPlace`.** Supports text + placetype + country + limit. Full ranking surface (parentId descendant filter, proximity boost, bbox hard filter, population weighting) lands in v0.2.0 once the shared query builder is extracted from `@mailwoman/resolver-wof-sqlite`.
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { loadSlimWofDatabase, WofWasmPlaceLookup } from "@mailwoman/resolver-wof-wasm"
15
+
16
+ // Load the slim DB. Either fetch from a URL or pass raw Uint8Array bytes.
17
+ const { db } = await loadSlimWofDatabase({
18
+ source: "/static/wof-hot.db", // or a Uint8Array from bundler import
19
+ wasmUrl: new URL("../node_modules/@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm/sqlite3.wasm", import.meta.url).href,
20
+ })
21
+
22
+ const lookup = new WofWasmPlaceLookup({ db })
23
+
24
+ const matches = await lookup.findPlace({
25
+ text: "Springfield",
26
+ placetype: "locality",
27
+ country: "US",
28
+ limit: 5,
29
+ })
30
+
31
+ for (const m of matches) {
32
+ console.log(m.id, m.name, m.lat, m.lon, "score:", m.score)
33
+ }
34
+
35
+ lookup.close()
36
+ ```
37
+
38
+ ## Loading strategies
39
+
40
+ `loadSlimWofDatabase` currently fetches the whole DB and opens it in memory via `sqlite3_deserialize`. For the ~35 MB default slim build that's a one-RTT transfer + a one-shot in-memory open — typically sub-second on broadband, and after that every query is in-process WASM.
41
+
42
+ For larger DBs or low-bandwidth users, the future path is to swap the loader for an HTTP-VFS implementation (à la `sql.js-httpvfs`) so SQLite pages get fetched lazily via byte-range. The `WofWasmPlaceLookup` class is loader-agnostic — only the loader changes.
43
+
44
+ ## Bundling
45
+
46
+ This package ships compiled TypeScript only. The `@sqlite.org/sqlite-wasm` runtime (`.wasm` + worker JS) is a peer asset your bundler needs to serve. For Vite:
47
+
48
+ ```ts
49
+ import wasmUrl from "@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm/sqlite3.wasm?url"
50
+ ```
51
+
52
+ For webpack: use `asset/resource` rules on the `.wasm` extension and pass the resolved URL via the `wasmUrl` option.
53
+
54
+ ## Why not extend `WofSqlitePlaceLookup`?
55
+
56
+ `WofSqlitePlaceLookup` is hard-bound to `node:sqlite` (the Node 22+ built-in). Subclassing across the Node/WASM line means dragging Node-only types into a browser package. We chose composition over inheritance: both classes implement the same `PlaceLookup` interface and (v0.2.0+) call the same shared query builder, but stay independently importable.
57
+
58
+ ## License
59
+
60
+ AGPL-3.0-only (mirrors the rest of the mailwoman tree).
package/out/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+ export { loadSlimWofDatabase, type LoadSlimOpts } from "./loader.js";
7
+ export { WofWasmPlaceLookup, type WofWasmPlaceLookupOpts } from "./lookup.js";
8
+ export type { FindPlaceQuery, GeoBbox, GeoPoint, PlaceCandidate, PlaceLookup, WofPlacetype, } from "@mailwoman/resolver-wof-sqlite";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAG7E,YAAY,EACX,cAAc,EACd,OAAO,EACP,QAAQ,EACR,cAAc,EACd,WAAW,EACX,YAAY,GACZ,MAAM,gCAAgC,CAAA"}
package/out/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+ export { loadSlimWofDatabase } from "./loader.js";
7
+ export { WofWasmPlaceLookup } from "./lookup.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAqB,MAAM,aAAa,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAA+B,MAAM,aAAa,CAAA"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Loads a slim WOF SQLite distribution into an in-memory `@sqlite.org/sqlite-wasm` database.
7
+ *
8
+ * V1 strategy: fetch the whole file (~35 MB for the default top-1k US slim) and open it via the OO1
9
+ * API's "OPFS"-flavored constructor in transient mode. The full-fetch approach is fine for a
10
+ * bundle this size — the slim DB is what the browser holds in RAM for the duration of the session
11
+ * anyway, and HTTP/2 + gzip make the 35 MB transfer pay one RTT + transfer time, not the
12
+ * "hundreds of byte-range requests" cost a HTTP-VFS approach would incur.
13
+ *
14
+ * When we eventually want incremental loading (Phase B.x), this is the seam to swap — keep
15
+ * `WofWasmPlaceLookup` unchanged and replace the loader with a `sql.js-httpvfs`-style VFS.
16
+ */
17
+ import { type Database, type Sqlite3Static } from "@sqlite.org/sqlite-wasm";
18
+ export interface LoadSlimOpts {
19
+ /**
20
+ * Either a URL to fetch the slim .db from, or a raw Uint8Array containing the file bytes.
21
+ *
22
+ * URL form is the public-demo path (load over HTTP). Uint8Array form is what tests use to skip
23
+ * the network entirely and is also useful if a caller wants to embed the DB in their own bundler
24
+ * output (Vite's `?url` / `?arraybuffer` imports both produce things that fit here).
25
+ */
26
+ source: string | Uint8Array;
27
+ /**
28
+ * Where the sqlite-wasm runtime can find its .wasm asset. Required in browser builds because the
29
+ * default URL is resolved relative to the worker script, which bundlers usually rewrite.
30
+ *
31
+ * Most bundlers will let you do `new
32
+ * URL("../node_modules/@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm/sqlite3.wasm",
33
+ * import.meta.url).href`.
34
+ *
35
+ * Leave unset to use the runtime's defaults (works in Node + worker contexts where the path is
36
+ * resolvable directly).
37
+ */
38
+ wasmUrl?: string;
39
+ /**
40
+ * Optional fetch implementation override. Defaults to `globalThis.fetch`. Useful in test
41
+ * harnesses that want to short-circuit network calls.
42
+ */
43
+ fetchImpl?: typeof fetch;
44
+ }
45
+ /**
46
+ * Loads + opens the slim WOF DB. Returns `{ db, sqlite3 }` — `db` is the open Database; `sqlite3`
47
+ * is the runtime handle (in case the caller wants to call other OO1 APIs on it).
48
+ *
49
+ * Caller is responsible for `db.close()` when done.
50
+ */
51
+ export declare function loadSlimWofDatabase(opts: LoadSlimOpts): Promise<{
52
+ db: Database;
53
+ sqlite3: Sqlite3Static;
54
+ }>;
55
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAA0B,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAE9F,MAAM,WAAW,YAAY;IAC5B;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,GAAG,UAAU,CAAA;IAC3B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC,CAuC/G"}
package/out/loader.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Loads a slim WOF SQLite distribution into an in-memory `@sqlite.org/sqlite-wasm` database.
7
+ *
8
+ * V1 strategy: fetch the whole file (~35 MB for the default top-1k US slim) and open it via the OO1
9
+ * API's "OPFS"-flavored constructor in transient mode. The full-fetch approach is fine for a
10
+ * bundle this size — the slim DB is what the browser holds in RAM for the duration of the session
11
+ * anyway, and HTTP/2 + gzip make the 35 MB transfer pay one RTT + transfer time, not the
12
+ * "hundreds of byte-range requests" cost a HTTP-VFS approach would incur.
13
+ *
14
+ * When we eventually want incremental loading (Phase B.x), this is the seam to swap — keep
15
+ * `WofWasmPlaceLookup` unchanged and replace the loader with a `sql.js-httpvfs`-style VFS.
16
+ */
17
+ import sqlite3InitModule, {} from "@sqlite.org/sqlite-wasm";
18
+ /**
19
+ * Loads + opens the slim WOF DB. Returns `{ db, sqlite3 }` — `db` is the open Database; `sqlite3`
20
+ * is the runtime handle (in case the caller wants to call other OO1 APIs on it).
21
+ *
22
+ * Caller is responsible for `db.close()` when done.
23
+ */
24
+ export async function loadSlimWofDatabase(opts) {
25
+ const bytes = typeof opts.source === "string" ? await fetchBytes(opts.source, opts.fetchImpl) : opts.source;
26
+ // sqlite3InitModule's TS signature lies about its options bag — the runtime DOES accept the
27
+ // Emscripten-style {print, printErr, locateFile} options shown in the upstream docs. Cast to
28
+ // `any` for the call site rather than shadowing the typed wrapper for the entire file.
29
+ const sqlite3 = await sqlite3InitModule({
30
+ print: () => { }, // suppress stdout from the WASM runtime
31
+ printErr: (msg) => console.error("[sqlite-wasm]", msg),
32
+ ...(opts.wasmUrl ? { locateFile: (name) => (name.endsWith(".wasm") ? opts.wasmUrl : name) } : {}),
33
+ });
34
+ // OO1 transient-DB constructor: opens an in-memory DB then we restore the file bytes into it
35
+ // via `sqlite3.capi.sqlite3_deserialize`. This is the official way to "open a Uint8Array as a
36
+ // database" — `new DB(":memory:")` followed by deserialize is faster than CREATE TABLE +
37
+ // INSERT-from-dump and preserves the on-disk b-tree pages directly.
38
+ const db = new sqlite3.oo1.DB(":memory:", "ct");
39
+ // `allocFromTypedArray` has shape constraints across sqlite-wasm versions; the
40
+ // explicit alloc + HEAPU8.set pattern is the lowest-common-denominator path and
41
+ // avoids the "expecting 8/16/32/64" heap-shape mismatch seen on Node builds.
42
+ const p = sqlite3.wasm.alloc(bytes.byteLength);
43
+ const heap = sqlite3.wasm.heap8u();
44
+ heap.set(bytes, p);
45
+ const rc = sqlite3.capi.sqlite3_deserialize(db.pointer, "main", p, bytes.byteLength, bytes.byteLength, sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE | sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE);
46
+ if (rc !== sqlite3.capi.SQLITE_OK) {
47
+ sqlite3.wasm.dealloc(p);
48
+ db.close();
49
+ throw new Error(`sqlite3_deserialize failed: rc=${rc}`);
50
+ }
51
+ return { db, sqlite3 };
52
+ }
53
+ async function fetchBytes(url, fetchImpl) {
54
+ const f = fetchImpl ?? globalThis.fetch;
55
+ if (!f)
56
+ throw new Error("no fetch implementation available — pass fetchImpl in non-fetch environments");
57
+ const res = await f(url);
58
+ if (!res.ok)
59
+ throw new Error(`fetch ${url} failed: ${res.status} ${res.statusText}`);
60
+ return new Uint8Array(await res.arrayBuffer());
61
+ }
62
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,iBAAiB,EAAE,EAAqC,MAAM,yBAAyB,CAAA;AA8B9F;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAkB;IAC3D,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;IAE3G,4FAA4F;IAC5F,6FAA6F;IAC7F,uFAAuF;IACvF,MAAM,OAAO,GAAG,MAAO,iBAA0F,CAAC;QACjH,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,wCAAwC;QACzD,QAAQ,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC;QAC9D,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1G,CAAC,CAAA;IAEF,6FAA6F;IAC7F,8FAA8F;IAC9F,yFAAyF;IACzF,oEAAoE;IACpE,MAAM,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IAE/C,+EAA+E;IAC/E,gFAAgF;IAChF,6EAA6E;IAC7E,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAA;IAClC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAClB,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAC1C,EAAE,CAAC,OAAQ,EACX,MAAM,EACN,CAAC,EACD,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,UAAU,EAChB,OAAO,CAAC,IAAI,CAAC,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC,6BAA6B,CACxF,CAAA;IACD,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;QACvB,EAAE,CAAC,KAAK,EAAE,CAAA;QACV,MAAM,IAAI,KAAK,CAAC,kCAAkC,EAAE,EAAE,CAAC,CAAA;IACxD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAA;AACvB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,GAAW,EAAE,SAAwB;IAC9D,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,CAAC,KAAK,CAAA;IACvC,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAA;IACvG,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,SAAS,GAAG,YAAY,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAA;IACpF,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;AAC/C,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `WofWasmPlaceLookup` — browser-side `PlaceLookup` backed by `@sqlite.org/sqlite-wasm`.
7
+ *
8
+ * V1 scope: text + placetype + limit + country. The full ranking surface from
9
+ * `WofSqlitePlaceLookup` (parentId descendant filter, near-proximity boost, bbox hard filter,
10
+ * population-weighted ordering) is queued for v2 in the same PR series — see Phase B tracking
11
+ * issue #98. The v1 scope is the minimum that lets the public demo answer "type a US city /
12
+ * postcode, get a hit".
13
+ *
14
+ * Internally this is a thin facade over the OO1 DB returned by `loadSlimWofDatabase`. The SQL we
15
+ * issue is the same SQLite dialect the Node implementation uses — once we extract the SQL
16
+ * building into a shared helper (planned: `@mailwoman/resolver-wof-sqlite/query-builder`), both
17
+ * implementations will call into the same builder and the parity guarantee becomes mechanical
18
+ * rather than convention-driven.
19
+ */
20
+ import type { Database } from "@sqlite.org/sqlite-wasm";
21
+ import { type CoincidentLocality } from "@mailwoman/resolver";
22
+ import type { FindPlaceQuery, PlaceCandidate, PlaceLookup } from "@mailwoman/resolver-wof-sqlite";
23
+ export interface WofWasmPlaceLookupOpts {
24
+ /** Open `@sqlite.org/sqlite-wasm` Database (from `loadSlimWofDatabase`). */
25
+ db: Database;
26
+ }
27
+ export declare class WofWasmPlaceLookup implements PlaceLookup {
28
+ #private;
29
+ constructor(opts: WofWasmPlaceLookupOpts);
30
+ findPlace(query: FindPlaceQuery): Promise<PlaceCandidate[]>;
31
+ /**
32
+ * Dual-role localities coincident with an admin id, from the `coincident_roles` relation (#403)
33
+ * carried into the slim DB by build-slim. Backs the resolver's hierarchy completion (on by
34
+ * default) in the browser — mirrors `WofSqlitePlaceLookup.coincidentLocalitiesFor`. Returns `[]`
35
+ * when the slim DB predates the relation. Loaded once + memoized (the relation is ~hundreds of
36
+ * rows).
37
+ */
38
+ coincidentLocalitiesFor(adminId: number | string): CoincidentLocality[];
39
+ close(): void;
40
+ }
41
+ //# sourceMappingURL=lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAEvD,OAAO,EAAyB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAgB,MAAM,gCAAgC,CAAA;AAK/G,MAAM,WAAW,sBAAsB;IACtC,4EAA4E;IAC5E,EAAE,EAAE,QAAQ,CAAA;CACZ;AAiBD,qBAAa,kBAAmB,YAAW,WAAW;;gBAUzC,IAAI,EAAE,sBAAsB;IAqClC,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAoIjE;;;;;;OAMG;IACH,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,EAAE;IAgDvE,KAAK,IAAI,IAAI;CAGb"}
package/out/lookup.js ADDED
@@ -0,0 +1,255 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `WofWasmPlaceLookup` — browser-side `PlaceLookup` backed by `@sqlite.org/sqlite-wasm`.
7
+ *
8
+ * V1 scope: text + placetype + limit + country. The full ranking surface from
9
+ * `WofSqlitePlaceLookup` (parentId descendant filter, near-proximity boost, bbox hard filter,
10
+ * population-weighted ordering) is queued for v2 in the same PR series — see Phase B tracking
11
+ * issue #98. The v1 scope is the minimum that lets the public demo answer "type a US city /
12
+ * postcode, get a hit".
13
+ *
14
+ * Internally this is a thin facade over the OO1 DB returned by `loadSlimWofDatabase`. The SQL we
15
+ * issue is the same SQLite dialect the Node implementation uses — once we extract the SQL
16
+ * building into a shared helper (planned: `@mailwoman/resolver-wof-sqlite/query-builder`), both
17
+ * implementations will call into the same builder and the parity guarantee becomes mechanical
18
+ * rather than convention-driven.
19
+ */
20
+ import { expandPlacetypeFilter } from "@mailwoman/resolver";
21
+ // Browser-safe subpath import (fts.ts's only node:sqlite import is type-only) — the shared
22
+ // alias-bag parser keeps this backend's exact tier byte-identical to the Node resolver's.
23
+ import { aliasBagExactMatch } from "@mailwoman/resolver-wof-sqlite/fts";
24
+ /**
25
+ * Population-boost tunables, mirroring `resolver-wof-sqlite/lookup.ts` defaults. The boost is
26
+ * `POPULATION_BOOST * min(1, log10(1 + pop) / POPULATION_SCALE_LOG10)`, subtracted from bm25 (lower
27
+ * = better, matching SQLite's convention). A 1M-population city earns the full boost — enough to
28
+ * surface the famous same-name place ("New York" over "West New York") without steamrolling a
29
+ * clearly-better text match, because exact-name tiering is consulted FIRST.
30
+ */
31
+ const POPULATION_BOOST = 4.0;
32
+ const POPULATION_SCALE_LOG10 = 6.0;
33
+ /** Normalize a name/query for exact-match tiering: lowercase, trim, collapse internal whitespace. */
34
+ function normalizeName(s) {
35
+ return s.toLowerCase().trim().replace(/\s+/g, " ");
36
+ }
37
+ export class WofWasmPlaceLookup {
38
+ #db;
39
+ #hasPopulationCache;
40
+ #hasPlaceAbbrCache;
41
+ /**
42
+ * Lazily-built `admin_id → coincident localities` map from the #403 relation (the slim DB carries
43
+ * it).
44
+ */
45
+ #coincidentRolesCache;
46
+ constructor(opts) {
47
+ this.#db = opts.db;
48
+ }
49
+ /** Lazily probe (once) whether the slim DB carries the `place_population` aux table. */
50
+ #hasPopulation() {
51
+ if (this.#hasPopulationCache === undefined) {
52
+ const r = this.#db.selectObjects(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='place_population' LIMIT 1`);
53
+ this.#hasPopulationCache = r.length > 0;
54
+ }
55
+ return this.#hasPopulationCache;
56
+ }
57
+ /** Lazily probe (once) whether the slim DB carries the `place_abbr` aux table (build-slim ≥ #189). */
58
+ #hasPlaceAbbr() {
59
+ if (this.#hasPlaceAbbrCache === undefined) {
60
+ const r = this.#db.selectObjects(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='place_abbr' LIMIT 1`);
61
+ this.#hasPlaceAbbrCache = r.length > 0;
62
+ }
63
+ return this.#hasPlaceAbbrCache;
64
+ }
65
+ /**
66
+ * Ids whose region abbreviation exactly equals `text` (case-insensitive), from `place_abbr`. The
67
+ * exact-abbrev tier signal — see the `findPlace` call site. Empty on slim DBs without the table.
68
+ */
69
+ #abbrExactIds(text) {
70
+ const t = text.trim();
71
+ if (!t || !this.#hasPlaceAbbr())
72
+ return new Set();
73
+ const rows = this.#db.selectObjects(`SELECT id FROM place_abbr WHERE abbr = ? COLLATE NOCASE`, [t]);
74
+ return new Set(rows.map((r) => Number(r.id)));
75
+ }
76
+ async findPlace(query) {
77
+ const text = (query.text ?? "").trim();
78
+ if (!text)
79
+ return [];
80
+ const ftsQuery = sanitizeFtsQuery(text);
81
+ if (!ftsQuery)
82
+ return [];
83
+ const limit = Math.max(1, query.limit ?? 10);
84
+ // FTS5 MATCH on place_search joined to spr. Placetype + country filters are pushed into the
85
+ // WHERE clause because they reduce candidate count cheaply.
86
+ const conditions = ["place_search MATCH ?", "spr.is_current != 0", "spr.is_deprecated = 0"];
87
+ const params = [ftsQuery];
88
+ // Shared placetype-equivalence expansion (core/resolver): a `locality` query must also reach
89
+ // `borough` / `localadmin` rows. Without it, Brooklyn-the-borough (pop 2.5M, an EXACT name
90
+ // match) was unreachable and the fuzzy "Brooklyn Park, MN" won. Same table the Node resolver
91
+ // uses — the two backends can't drift.
92
+ const placetypes = expandPlacetypeFilter(normalizePlacetypes(query.placetype));
93
+ if (placetypes && placetypes.length > 0) {
94
+ conditions.push(`spr.placetype IN (${placetypes.map(() => "?").join(",")})`);
95
+ params.push(...placetypes);
96
+ }
97
+ if (query.country) {
98
+ conditions.push("spr.country = ?");
99
+ params.push(query.country.toUpperCase());
100
+ }
101
+ // Point-in-bbox filter. Used to constrain a locality lookup to a parsed region/state's bounds
102
+ // (e.g. "Roseville, Michigan" → only the Roseville whose centroid sits in Michigan's bbox),
103
+ // which the broken-in-the-slim-DB parent_id chain can't do via descendant filtering.
104
+ if (query.bbox) {
105
+ conditions.push("spr.latitude BETWEEN ? AND ?", "spr.longitude BETWEEN ? AND ?");
106
+ params.push(query.bbox.minLat, query.bbox.maxLat, query.bbox.minLon, query.bbox.maxLon);
107
+ }
108
+ // Over-fetch a pool ordered by raw BM25, then re-rank in JS (exact-name tier, then
109
+ // population-weighted bm25). The over-fetch is essential: a famous place can sit a few rows
110
+ // below a tiny same-name town on raw BM25 ("New York" loses to "West New York" by a hair), so a
111
+ // tight LIMIT on bm25 alone would truncate it before the re-rank could pull it up. This mirrors
112
+ // the post-scoring tier + population boost in resolver-wof-sqlite/lookup.ts. (v1 issued pure
113
+ // bm25, which is why the demo targeted West New York for "New York, NY".)
114
+ const hasPop = this.#hasPopulation();
115
+ const pool = Math.max(limit, 50);
116
+ const sql = `SELECT spr.id, spr.name, spr.placetype, spr.country, spr.latitude, spr.longitude, spr.parent_id, ` +
117
+ `spr.min_latitude, spr.max_latitude, spr.min_longitude, spr.max_longitude, ` +
118
+ `place_search.alt_names AS alt_names, ` +
119
+ `${hasPop ? "pp.population" : "NULL"} AS population, bm25(place_search) AS bm25 ` +
120
+ `FROM place_search JOIN spr ON spr.id = place_search.wof_id ` +
121
+ `${hasPop ? "LEFT JOIN place_population pp ON pp.id = spr.id " : ""}` +
122
+ `WHERE ${conditions.join(" AND ")} ` +
123
+ `ORDER BY bm25(place_search) ASC ` +
124
+ `LIMIT ?`;
125
+ params.push(pool);
126
+ const rows = this.#db.selectObjects(sql, params);
127
+ const normQuery = normalizeName(text);
128
+ // Exact-abbreviation ids: region/state abbreviations live in the slim DB's `place_abbr` table
129
+ // (carried by build-slim before `names` is dropped). A candidate whose abbreviation equals the
130
+ // query is an EXACT match — same tier as an exact name match — so "VT" → Vermont outranks a
131
+ // foreign region that merely token-matches "VT" via a multilingual name fragment. No-op on slim
132
+ // DBs built before place_abbr (the table is absent → empty set). This is the data-driven
133
+ // replacement for the demo's hardcoded `expandUsRegion` map; it also generalizes beyond US.
134
+ const abbrIds = this.#abbrExactIds(text);
135
+ // Strict exact = canonical name or region abbreviation equals the query. Computed for the whole
136
+ // pool FIRST because the ALIAS tier below only engages when no strict exact exists.
137
+ const strictExact = (row) => normalizeName(row.name) === normQuery || abbrIds.has(row.id);
138
+ const anyStrictExact = rows.some(strictExact);
139
+ return rows
140
+ .map((row) => {
141
+ // Alias tier: `alt_names` is the FTS row's alias bag (the slim DB's only surviving alias
142
+ // source), aliases joined on the boundary-preserving ALIAS_SEPARATOR (#523). The shared
143
+ // parser does a true per-alias equality check, ungated; on a LEGACY bag (pre-#523 slim
144
+ // artifact, boundaries lost) it falls back to padded containment gated on "no strictly
145
+ // exact candidate" so interior fragments ("York" inside "New York City") can't be
146
+ // false-promoted. Mirrors the Node resolver's alias tier
147
+ // (`WofSqlitePlaceLookup.#exactMatchIds`).
148
+ const aliasExact = aliasBagExactMatch(row.alt_names, normQuery, anyStrictExact);
149
+ const exactTier = strictExact(row) || aliasExact ? 0 : 1;
150
+ const popBoost = row.population && row.population > 0
151
+ ? POPULATION_BOOST * Math.min(1, Math.log10(1 + row.population) / POPULATION_SCALE_LOG10)
152
+ : 0;
153
+ // Lower adjScore = better, matching SQLite's bm25 convention (more negative = better).
154
+ const adjScore = row.bm25 - popBoost;
155
+ return { row, exactTier, adjScore };
156
+ })
157
+ .sort((a, b) => a.exactTier - b.exactTier || a.adjScore - b.adjScore)
158
+ .slice(0, limit)
159
+ .map(({ row, adjScore, exactTier }) => ({
160
+ id: row.id,
161
+ name: row.name,
162
+ placetype: row.placetype,
163
+ country: row.country,
164
+ lat: row.latitude,
165
+ lon: row.longitude,
166
+ parent_id: row.parent_id ?? undefined,
167
+ // Surface the exact-match tier so a downstream country re-rank (#369) can keep the country
168
+ // pin from crossing it — parity with `WofSqlitePlaceLookup`. See ResolvedPlace.exactMatch.
169
+ exactMatch: exactTier === 0,
170
+ bbox: row.min_latitude != null && row.max_latitude != null && row.min_longitude != null && row.max_longitude != null
171
+ ? {
172
+ minLat: row.min_latitude,
173
+ maxLat: row.max_latitude,
174
+ minLon: row.min_longitude,
175
+ maxLon: row.max_longitude,
176
+ }
177
+ : undefined,
178
+ // Flip sign so higher = better (PlaceLookup contract). The adjusted, population-aware
179
+ // score is what we sorted by, so callers see the same ordering they're shown.
180
+ score: -adjScore,
181
+ }));
182
+ }
183
+ /**
184
+ * Dual-role localities coincident with an admin id, from the `coincident_roles` relation (#403)
185
+ * carried into the slim DB by build-slim. Backs the resolver's hierarchy completion (on by
186
+ * default) in the browser — mirrors `WofSqlitePlaceLookup.coincidentLocalitiesFor`. Returns `[]`
187
+ * when the slim DB predates the relation. Loaded once + memoized (the relation is ~hundreds of
188
+ * rows).
189
+ */
190
+ coincidentLocalitiesFor(adminId) {
191
+ const id = typeof adminId === "number" ? adminId : Number(adminId);
192
+ if (!Number.isFinite(id))
193
+ return [];
194
+ if (!this.#coincidentRolesCache) {
195
+ const map = new Map();
196
+ const exists = this.#db.selectObjects(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='coincident_roles' LIMIT 1`);
197
+ if (exists.length > 0) {
198
+ const rows = this.#db.selectObjects(`SELECT cr.admin_id AS adminId, s.id AS id, s.name AS name, s.country AS country,
199
+ s.latitude AS lat, s.longitude AS lon, cr.relationship_type AS relationshipType,
200
+ cr.locality_population AS population, cr.distance_km AS distanceKm
201
+ FROM coincident_roles cr JOIN spr s ON s.id = cr.locality_id`);
202
+ for (const r of rows) {
203
+ const candidate = {
204
+ id: r.id,
205
+ name: r.name,
206
+ placetype: "locality",
207
+ country: r.country,
208
+ lat: r.lat,
209
+ lon: r.lon,
210
+ score: 0,
211
+ relationshipType: r.relationshipType,
212
+ population: r.population,
213
+ distanceKm: r.distanceKm,
214
+ };
215
+ const list = map.get(r.adminId);
216
+ if (list)
217
+ list.push(candidate);
218
+ else
219
+ map.set(r.adminId, [candidate]);
220
+ }
221
+ }
222
+ this.#coincidentRolesCache = map;
223
+ }
224
+ return this.#coincidentRolesCache.get(id) ?? [];
225
+ }
226
+ close() {
227
+ this.#db.close();
228
+ }
229
+ }
230
+ /**
231
+ * Trim raw user input into something FTS5 will accept. Preserves trailing `*` as the FTS5 prefix
232
+ * operator (matching the resolver-wof-sqlite implementation). Strips characters FTS5 treats as
233
+ * punctuation or operators so a user typing `Paris's` or `St. (Petersburg)` doesn't trigger an
234
+ * "fts5: syntax error" inside the WASM runtime.
235
+ */
236
+ function sanitizeFtsQuery(text) {
237
+ const out = [];
238
+ for (const rawToken of text.normalize("NFKC").split(/\s+/u)) {
239
+ const trimmed = rawToken.trim();
240
+ if (!trimmed)
241
+ continue;
242
+ const hasPrefixStar = trimmed.endsWith("*");
243
+ const body = trimmed.replace(/[^\p{L}\p{N}]/gu, "");
244
+ if (!body)
245
+ continue;
246
+ out.push(hasPrefixStar ? `${body}*` : `"${body.replace(/"/g, '""')}"`);
247
+ }
248
+ return out.join(" ");
249
+ }
250
+ function normalizePlacetypes(input) {
251
+ if (!input)
252
+ return null;
253
+ return Array.isArray(input) ? input : [input];
254
+ }
255
+ //# sourceMappingURL=lookup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookup.js","sourceRoot":"","sources":["../lookup.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@mailwoman/resolver-wof-wasm",
3
+ "version": "0.1.0",
4
+ "description": "Who's On First WASM resolver — browser-side PlaceLookup backed by @sqlite.org/sqlite-wasm. Drop-in replacement for @mailwoman/resolver-wof-sqlite when you need a static-asset deploy (Phase B of the demo plan).",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/sister-software/mailwoman.git",
9
+ "directory": "resolver-wof-wasm"
10
+ },
11
+ "type": "module",
12
+ "exports": {
13
+ "./package.json": "./package.json",
14
+ ".": "./out/index.js"
15
+ },
16
+ "dependencies": {
17
+ "@mailwoman/core": "4.14.0",
18
+ "@mailwoman/resolver": "4.14.0",
19
+ "@mailwoman/resolver-wof-sqlite": "4.14.0",
20
+ "@sqlite.org/sqlite-wasm": "^3.53.0-build1"
21
+ },
22
+ "files": [
23
+ "out/**/*.js",
24
+ "out/**/*.js.map",
25
+ "out/**/*.d.ts",
26
+ "out/**/*.d.ts.map",
27
+ "README.md"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }