@mailwoman/resolver-wof-sqlite 4.16.2 → 5.0.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/out/address-point-interpolation.d.ts.map +1 -1
- package/out/address-point-interpolation.js +6 -6
- package/out/address-point-interpolation.js.map +1 -1
- package/out/address-point-schema.d.ts +5 -6
- package/out/address-point-schema.d.ts.map +1 -1
- package/out/address-point-schema.js +7 -2
- package/out/address-point-schema.js.map +1 -1
- package/out/address-point.d.ts +22 -8
- package/out/address-point.d.ts.map +1 -1
- package/out/address-point.js +29 -13
- package/out/address-point.js.map +1 -1
- package/out/ancestry-backfill.d.ts +53 -0
- package/out/ancestry-backfill.d.ts.map +1 -0
- package/out/ancestry-backfill.js +150 -0
- package/out/ancestry-backfill.js.map +1 -0
- package/out/ancestry.d.ts +6 -6
- package/out/ancestry.d.ts.map +1 -1
- package/out/ancestry.js +6 -6
- package/out/ancestry.js.map +1 -1
- package/out/build-candidate-cli.d.ts.map +1 -1
- package/out/build-candidate-cli.js.map +1 -1
- package/out/build-candidate.d.ts +4 -5
- package/out/build-candidate.d.ts.map +1 -1
- package/out/build-candidate.js +15 -14
- package/out/build-candidate.js.map +1 -1
- package/out/build-coincident-roles-cli.d.ts.map +1 -1
- package/out/build-coincident-roles-cli.js.map +1 -1
- package/out/build-fts-cli.d.ts +1 -1
- package/out/build-fts-cli.d.ts.map +1 -1
- package/out/build-fts-cli.js +6 -6
- package/out/build-fts-cli.js.map +1 -1
- package/out/build-slim-cli.d.ts +1 -1
- package/out/build-slim-cli.d.ts.map +1 -1
- package/out/build-slim-cli.js +3 -3
- package/out/build-slim-cli.js.map +1 -1
- package/out/build-slim.d.ts +7 -8
- package/out/build-slim.d.ts.map +1 -1
- package/out/build-slim.js +9 -9
- package/out/build-slim.js.map +1 -1
- package/out/candidate-fts.d.ts +5 -7
- package/out/candidate-fts.d.ts.map +1 -1
- package/out/candidate-fts.js +5 -7
- package/out/candidate-fts.js.map +1 -1
- package/out/candidate-lookup.d.ts +7 -7
- package/out/candidate-lookup.d.ts.map +1 -1
- package/out/candidate-lookup.js +31 -35
- package/out/candidate-lookup.js.map +1 -1
- package/out/candidate-schema.d.ts +13 -14
- package/out/candidate-schema.d.ts.map +1 -1
- package/out/candidate-schema.js +9 -9
- package/out/candidate-schema.js.map +1 -1
- package/out/coincident-roles.d.ts +9 -11
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +7 -7
- package/out/coincident-roles.js.map +1 -1
- package/out/convention.d.ts +30 -35
- package/out/convention.d.ts.map +1 -1
- package/out/convention.js +19 -21
- package/out/convention.js.map +1 -1
- package/out/fst-autocomplete.d.ts +5 -5
- package/out/fst-autocomplete.d.ts.map +1 -1
- package/out/fst-autocomplete.js +12 -13
- package/out/fst-autocomplete.js.map +1 -1
- package/out/fst-builder.d.ts +7 -7
- package/out/fst-builder.d.ts.map +1 -1
- package/out/fst-builder.js +9 -9
- package/out/fst-builder.js.map +1 -1
- package/out/fst-deserialize-web.d.ts +4 -4
- package/out/fst-deserialize-web.d.ts.map +1 -1
- package/out/fst-deserialize-web.js +4 -4
- package/out/fst-deserialize-web.js.map +1 -1
- package/out/fst-matcher.d.ts +12 -12
- package/out/fst-matcher.d.ts.map +1 -1
- package/out/fst-matcher.js +25 -25
- package/out/fst-matcher.js.map +1 -1
- package/out/fst-serialize.d.ts +5 -5
- package/out/fst-serialize.d.ts.map +1 -1
- package/out/fst-serialize.js +7 -6
- package/out/fst-serialize.js.map +1 -1
- package/out/fst-types.d.ts +13 -13
- package/out/fts.d.ts +74 -80
- package/out/fts.d.ts.map +1 -1
- package/out/fts.js +65 -71
- package/out/fts.js.map +1 -1
- package/out/geo.d.ts +14 -15
- package/out/geo.d.ts.map +1 -1
- package/out/geo.js +8 -8
- package/out/geo.js.map +1 -1
- package/out/geonames-aliases.d.ts +18 -9
- package/out/geonames-aliases.d.ts.map +1 -1
- package/out/geonames-aliases.js +70 -12
- package/out/geonames-aliases.js.map +1 -1
- package/out/index.d.ts +9 -9
- package/out/index.js +7 -7
- package/out/interpolation.d.ts +8 -10
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +6 -6
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +74 -81
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +114 -128
- package/out/lookup.js.map +1 -1
- package/out/postal-city-alias-lookup.d.ts +9 -10
- package/out/postal-city-alias-lookup.d.ts.map +1 -1
- package/out/postal-city-alias-lookup.js +13 -14
- package/out/postal-city-alias-lookup.js.map +1 -1
- package/out/postal-city-alias-schema.d.ts +5 -6
- package/out/postal-city-alias-schema.d.ts.map +1 -1
- package/out/postal-city-alias-schema.js +3 -4
- package/out/postal-city-alias-schema.js.map +1 -1
- package/out/postal-city-candidate-schema.d.ts +7 -10
- package/out/postal-city-candidate-schema.d.ts.map +1 -1
- package/out/postal-city-candidate-schema.js +4 -6
- package/out/postal-city-candidate-schema.js.map +1 -1
- package/out/postcode-point-lookup.d.ts +3 -4
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +2 -2
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/reverse.d.ts +26 -29
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +36 -38
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +32 -33
- package/out/schema.d.ts.map +1 -1
- package/out/sharding.d.ts +26 -28
- package/out/sharding.d.ts.map +1 -1
- package/out/sharding.js +16 -16
- package/out/sharding.js.map +1 -1
- package/out/sqlite-convention-source.d.ts +3 -3
- package/out/sqlite-convention-source.d.ts.map +1 -1
- package/out/sqlite-convention-source.js +6 -6
- package/out/sqlite-convention-source.js.map +1 -1
- package/out/sqlite-utils.d.ts +4 -5
- package/out/sqlite-utils.d.ts.map +1 -1
- package/out/sqlite-utils.js +4 -5
- package/out/sqlite-utils.js.map +1 -1
- package/out/street-morphology-fst-builder.d.ts +15 -18
- package/out/street-morphology-fst-builder.d.ts.map +1 -1
- package/out/street-morphology-fst-builder.js +17 -17
- package/out/street-morphology-fst-builder.js.map +1 -1
- package/out/street-normalize.d.ts +43 -28
- package/out/street-normalize.d.ts.map +1 -1
- package/out/street-normalize.js +96 -34
- package/out/street-normalize.js.map +1 -1
- package/out/street-segment-schema.d.ts +5 -5
- package/out/street-segment-schema.js +2 -2
- package/out/types.d.ts +42 -47
- package/out/types.d.ts.map +1 -1
- package/out/types.js +1 -1
- package/out/unified-schema.d.ts +6 -6
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +10 -10
- package/out/unified-schema.js.map +1 -1
- package/package.json +26 -22
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Node reader over the POSTAL-CITY ALIAS table (`postal-city-alias-<cc>.db`) — the observed
|
|
7
7
|
* `postal_city → geo_locality` aliases per postcode (`build-postal-city-alias.ts`). Consumed by
|
|
8
|
-
* {@link
|
|
8
|
+
* {@link WOFSqlitePlaceLookup}'s coordinate-first locality scorer: a user-typed postal city
|
|
9
9
|
* ("Antioch", postcode 37013) becomes a name-match alias for the geographic locality the postcode
|
|
10
10
|
* actually sits in ("Nashville"), so the right place tiers to the top instead of a same-named
|
|
11
11
|
* town in another state. Opt-in — the lookup is only constructed when a path is supplied, and
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* uses), keeping one normalizer in one place.
|
|
17
17
|
*/
|
|
18
18
|
import { DatabaseSync } from "node:sqlite";
|
|
19
|
-
export interface
|
|
19
|
+
export interface WOFPostalCityAliasLookupOpts {
|
|
20
20
|
/** Path to a `postal-city-alias-<cc>.db` built by `build-postal-city-alias.ts`. Opened read-only. */
|
|
21
21
|
databasePath?: string;
|
|
22
22
|
/** Pre-opened handle (tests / shared connections). Mutually exclusive with `databasePath`. */
|
|
@@ -32,17 +32,16 @@ export interface PostalCityAlias {
|
|
|
32
32
|
n: number;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Reader over `postal_city_alias`. The only query is a postcode-scoped probe for DIVERGENT rows
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* Reader over `postal_city_alias`. The only query is a postcode-scoped probe for DIVERGENT rows (where the postal name
|
|
36
|
+
* differs from the geographic name — the rows that carry alias signal), issued via the typed Kysely query builder
|
|
37
|
+
* against {@link PostalCityAliasDatabase}.
|
|
38
38
|
*/
|
|
39
|
-
export declare class
|
|
39
|
+
export declare class WOFPostalCityAliasLookup {
|
|
40
40
|
#private;
|
|
41
|
-
constructor(opts:
|
|
41
|
+
constructor(opts: WOFPostalCityAliasLookupOpts);
|
|
42
42
|
/**
|
|
43
|
-
* Divergent postal-city aliases for a postcode (empty when the postcode isn't in the table). The
|
|
44
|
-
*
|
|
45
|
-
* matching candidate locality's alias set.
|
|
43
|
+
* Divergent postal-city aliases for a postcode (empty when the postcode isn't in the table). The scorer groups these
|
|
44
|
+
* by normalized `geoLocality` and appends the `postalCity` surfaces to the matching candidate locality's alias set.
|
|
46
45
|
*/
|
|
47
46
|
getDivergentAliases(postcode: string): Promise<PostalCityAlias[]>;
|
|
48
47
|
close(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postal-city-alias-lookup.d.ts","sourceRoot":"","sources":["../postal-city-alias-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"postal-city-alias-lookup.d.ts","sourceRoot":"","sources":["../postal-city-alias-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAM1C,MAAM,WAAW,4BAA4B;IAC5C,qGAAqG;IACrG,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,YAAY,CAAA;CACvB;AAED,+FAA+F;AAC/F,MAAM,WAAW,eAAe;IAC/B,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,WAAW,EAAE,MAAM,CAAA;IACnB,kDAAkD;IAClD,CAAC,EAAE,MAAM,CAAA;CACT;AAED;;;;GAIG;AACH,qBAAa,wBAAwB;;gBAKxB,IAAI,EAAE,4BAA4B;IAc9C;;;OAGG;IACG,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAcvE,KAAK,IAAI,IAAI;CAGb"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Node reader over the POSTAL-CITY ALIAS table (`postal-city-alias-<cc>.db`) — the observed
|
|
7
7
|
* `postal_city → geo_locality` aliases per postcode (`build-postal-city-alias.ts`). Consumed by
|
|
8
|
-
* {@link
|
|
8
|
+
* {@link WOFSqlitePlaceLookup}'s coordinate-first locality scorer: a user-typed postal city
|
|
9
9
|
* ("Antioch", postcode 37013) becomes a name-match alias for the geographic locality the postcode
|
|
10
10
|
* actually sits in ("Nashville"), so the right place tiers to the top instead of a same-named
|
|
11
11
|
* town in another state. Opt-in — the lookup is only constructed when a path is supplied, and
|
|
@@ -15,36 +15,35 @@
|
|
|
15
15
|
* candidate localities is the scorer's job (it owns the case/diacritic fold the soft name score
|
|
16
16
|
* uses), keeping one normalizer in one place.
|
|
17
17
|
*/
|
|
18
|
-
import { DatabaseClient } from "@mailwoman/core/kysley/client";
|
|
19
18
|
import { DatabaseSync } from "node:sqlite";
|
|
19
|
+
import { DatabaseClient } from "@mailwoman/core/kysley/client";
|
|
20
20
|
/**
|
|
21
|
-
* Reader over `postal_city_alias`. The only query is a postcode-scoped probe for DIVERGENT rows
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Reader over `postal_city_alias`. The only query is a postcode-scoped probe for DIVERGENT rows (where the postal name
|
|
22
|
+
* differs from the geographic name — the rows that carry alias signal), issued via the typed Kysely query builder
|
|
23
|
+
* against {@link PostalCityAliasDatabase}.
|
|
24
24
|
*/
|
|
25
|
-
export class
|
|
25
|
+
export class WOFPostalCityAliasLookup {
|
|
26
26
|
#db;
|
|
27
27
|
#kdb;
|
|
28
|
-
#
|
|
28
|
+
#ownsDB;
|
|
29
29
|
constructor(opts) {
|
|
30
30
|
if (opts.database) {
|
|
31
31
|
this.#db = opts.database;
|
|
32
|
-
this.#
|
|
32
|
+
this.#ownsDB = false;
|
|
33
33
|
}
|
|
34
34
|
else if (opts.databasePath) {
|
|
35
35
|
this.#db = new DatabaseSync(opts.databasePath, { readOnly: true });
|
|
36
|
-
this.#
|
|
36
|
+
this.#ownsDB = true;
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
|
-
throw new Error("
|
|
39
|
+
throw new Error("WOFPostalCityAliasLookup needs `databasePath` or `database`");
|
|
40
40
|
}
|
|
41
41
|
// `#kdb` wraps `#db` for the typed query; close() owns the raw handle directly (sync).
|
|
42
42
|
this.#kdb = new DatabaseClient({ database: this.#db });
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Divergent postal-city aliases for a postcode (empty when the postcode isn't in the table). The
|
|
46
|
-
*
|
|
47
|
-
* matching candidate locality's alias set.
|
|
45
|
+
* Divergent postal-city aliases for a postcode (empty when the postcode isn't in the table). The scorer groups these
|
|
46
|
+
* by normalized `geoLocality` and appends the `postalCity` surfaces to the matching candidate locality's alias set.
|
|
48
47
|
*/
|
|
49
48
|
async getDivergentAliases(postcode) {
|
|
50
49
|
const pc = postcode.trim();
|
|
@@ -59,7 +58,7 @@ export class WofPostalCityAliasLookup {
|
|
|
59
58
|
return rows.map((r) => ({ postalCity: String(r.postal_city), geoLocality: String(r.geo_locality), n: Number(r.n) }));
|
|
60
59
|
}
|
|
61
60
|
close() {
|
|
62
|
-
if (this.#
|
|
61
|
+
if (this.#ownsDB)
|
|
63
62
|
this.#db.close();
|
|
64
63
|
}
|
|
65
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postal-city-alias-lookup.js","sourceRoot":"","sources":["../postal-city-alias-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"postal-city-alias-lookup.js","sourceRoot":"","sources":["../postal-city-alias-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAqB9D;;;;GAIG;AACH,MAAM,OAAO,wBAAwB;IACpC,GAAG,CAAc;IACjB,IAAI,CAAyC;IAC7C,OAAO,CAAS;IAEhB,YAAY,IAAkC;QAC7C,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAA;YACxB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACrB,CAAC;aAAM,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;YAClE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACpB,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAA;QAC/E,CAAC;QACD,uFAAuF;QACvF,IAAI,CAAC,IAAI,GAAG,IAAI,cAAc,CAA0B,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAChF,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,mBAAmB,CAAC,QAAgB;QACzC,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAA;QAE1B,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,CAAA;QAClB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI;aAC1B,UAAU,CAAC,mBAAmB,CAAC;aAC/B,MAAM,CAAC,CAAC,aAAa,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC;aAC5C,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,CAAC;aAC1B,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;aAC1B,OAAO,EAAE,CAAA;QAEX,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACrH,CAAC;IAED,KAAK;QACJ,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAA;IACnC,CAAC;CACD"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Typed schema for the POSTAL-CITY ALIAS table (`postal-city-alias-<cc>.db`, built by
|
|
7
7
|
* `scripts/build-postal-city-alias.ts`) — the single source of truth for the columns shared by
|
|
8
|
-
* the BUILDER and the READER ({@link
|
|
8
|
+
* the BUILDER and the READER ({@link WOFPostalCityAliasLookup}). Like {@link CandidateTable}, the
|
|
9
9
|
* contract is a Kysely `Database` interface plus the table DDL as a string, so a column rename in
|
|
10
10
|
* the builder is a compile error in the reader.
|
|
11
11
|
*
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { Kysely } from "kysely";
|
|
19
19
|
/**
|
|
20
|
-
* One observed postal-city aggregate. The natural key is `(postcode, postal_city, geo_locality)`;
|
|
21
|
-
*
|
|
20
|
+
* One observed postal-city aggregate. The natural key is `(postcode, postal_city, geo_locality)`; the builder enforces
|
|
21
|
+
* a `MIN_COUNT` floor on `n`, so every row is a non-trivial usage.
|
|
22
22
|
*/
|
|
23
23
|
export interface PostalCityAliasTable {
|
|
24
24
|
/** The postcode the aggregate is scoped to (the resolver probes by this). */
|
|
@@ -43,9 +43,8 @@ export interface PostalCityAliasDatabase {
|
|
|
43
43
|
/** The `postal_city_alias` column order — the builder's INSERT derives its column list from this. */
|
|
44
44
|
export declare const POSTAL_CITY_ALIAS_COLUMNS: readonly ["postcode", "postal_city", "geo_locality", "n", "divergent", "source", "release"];
|
|
45
45
|
/**
|
|
46
|
-
* Create the `postal_city_alias` table + its two probe indexes. Kept here (not only in the builder)
|
|
47
|
-
*
|
|
48
|
-
* (or any `Kysely`) over the alias DB.
|
|
46
|
+
* Create the `postal_city_alias` table + its two probe indexes. Kept here (not only in the builder) so tests can stand
|
|
47
|
+
* up a fixture DB with the exact production shape. Pass a {@link DatabaseClient} (or any `Kysely`) over the alias DB.
|
|
49
48
|
*/
|
|
50
49
|
export declare function createPostalCityAliasTable(db: Kysely<PostalCityAliasDatabase>): Promise<void>;
|
|
51
50
|
//# sourceMappingURL=postal-city-alias-schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postal-city-alias-schema.d.ts","sourceRoot":"","sources":["../postal-city-alias-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEpC;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAA;IAChB,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAA;IACnB,qGAAqG;IACrG,YAAY,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,CAAC,EAAE,MAAM,CAAA;IACT,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAA;IACd,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAA;CACf;AAED,oGAAoG;AACpG,MAAM,WAAW,uBAAuB;IACvC,iBAAiB,EAAE,oBAAoB,CAAA;CACvC;AAED,qGAAqG;AACrG,eAAO,MAAM,yBAAyB,6FAQ5B,CAAA;AAEV
|
|
1
|
+
{"version":3,"file":"postal-city-alias-schema.d.ts","sourceRoot":"","sources":["../postal-city-alias-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEpC;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAA;IAChB,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAA;IACnB,qGAAqG;IACrG,YAAY,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,CAAC,EAAE,MAAM,CAAA;IACT,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAA;IACd,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAA;CACf;AAED,oGAAoG;AACpG,MAAM,WAAW,uBAAuB;IACvC,iBAAiB,EAAE,oBAAoB,CAAA;CACvC;AAED,qGAAqG;AACrG,eAAO,MAAM,yBAAyB,6FAQ5B,CAAA;AAEV;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,EAAE,EAAE,MAAM,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAanG"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Typed schema for the POSTAL-CITY ALIAS table (`postal-city-alias-<cc>.db`, built by
|
|
7
7
|
* `scripts/build-postal-city-alias.ts`) — the single source of truth for the columns shared by
|
|
8
|
-
* the BUILDER and the READER ({@link
|
|
8
|
+
* the BUILDER and the READER ({@link WOFPostalCityAliasLookup}). Like {@link CandidateTable}, the
|
|
9
9
|
* contract is a Kysely `Database` interface plus the table DDL as a string, so a column rename in
|
|
10
10
|
* the builder is a compile error in the reader.
|
|
11
11
|
*
|
|
@@ -26,9 +26,8 @@ export const POSTAL_CITY_ALIAS_COLUMNS = [
|
|
|
26
26
|
"release",
|
|
27
27
|
];
|
|
28
28
|
/**
|
|
29
|
-
* Create the `postal_city_alias` table + its two probe indexes. Kept here (not only in the builder)
|
|
30
|
-
*
|
|
31
|
-
* (or any `Kysely`) over the alias DB.
|
|
29
|
+
* Create the `postal_city_alias` table + its two probe indexes. Kept here (not only in the builder) so tests can stand
|
|
30
|
+
* up a fixture DB with the exact production shape. Pass a {@link DatabaseClient} (or any `Kysely`) over the alias DB.
|
|
32
31
|
*/
|
|
33
32
|
export async function createPostalCityAliasTable(db) {
|
|
34
33
|
await db.schema
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postal-city-alias-schema.js","sourceRoot":"","sources":["../postal-city-alias-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA8BH,qGAAqG;AACrG,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACxC,UAAU;IACV,aAAa;IACb,cAAc;IACd,GAAG;IACH,WAAW;IACX,QAAQ;IACR,SAAS;CACA,CAAA;AAEV
|
|
1
|
+
{"version":3,"file":"postal-city-alias-schema.js","sourceRoot":"","sources":["../postal-city-alias-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA8BH,qGAAqG;AACrG,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACxC,UAAU;IACV,aAAa;IACb,cAAc;IACd,GAAG;IACH,WAAW;IACX,QAAQ;IACR,SAAS;CACA,CAAA;AAEV;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,EAAmC;IACnF,MAAM,EAAE,CAAC,MAAM;SACb,WAAW,CAAC,mBAAmB,CAAC;SAChC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACjD,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACpD,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACrD,SAAS,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAC7C,SAAS,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACrD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/C,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAChD,OAAO,EAAE,CAAA;IACX,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAA;IACpG,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,EAAE,CAAA;AACvH,CAAC"}
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { type Kysely } from "kysely";
|
|
21
21
|
/**
|
|
22
|
-
* One postal-city → geo-locality edge, keyed exactly by `(name_key, postcode)`. The probe returns
|
|
23
|
-
*
|
|
22
|
+
* One postal-city → geo-locality edge, keyed exactly by `(name_key, postcode)`. The probe returns the geographic
|
|
23
|
+
* locality directly; the denormalized name/coord avoid a join back to `candidate`.
|
|
24
24
|
*/
|
|
25
25
|
export interface PostalCityCandidateTable {
|
|
26
26
|
/** {@link normalizeLocalityForKey} of the postal-city name — the build/query-consistent probe key. */
|
|
@@ -35,24 +35,21 @@ export interface PostalCityCandidateTable {
|
|
|
35
35
|
longitude: number;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* The postal-city-candidate database schema for `new
|
|
39
|
-
* DatabaseClient<PostalCityCandidateDatabase>(...)`.
|
|
38
|
+
* The postal-city-candidate database schema for `new DatabaseClient<PostalCityCandidateDatabase>(...)`.
|
|
40
39
|
*/
|
|
41
40
|
export interface PostalCityCandidateDatabase {
|
|
42
41
|
postal_city_candidate: PostalCityCandidateTable;
|
|
43
42
|
}
|
|
44
43
|
/**
|
|
45
|
-
* The table name the lookup probes (existence-gated, so an old candidate.db without it is
|
|
46
|
-
* byte-stable).
|
|
44
|
+
* The table name the lookup probes (existence-gated, so an old candidate.db without it is byte-stable).
|
|
47
45
|
*/
|
|
48
46
|
export declare const POSTAL_CITY_CANDIDATE_TABLE = "postal_city_candidate";
|
|
49
47
|
/** Column order for the builder's positional INSERT. */
|
|
50
48
|
export declare const POSTAL_CITY_CANDIDATE_COLUMNS: readonly ["name_key", "postcode", "spr_id", "name", "latitude", "longitude"];
|
|
51
49
|
/**
|
|
52
|
-
* Create the side-index — a clustered `WITHOUT ROWID` B-tree on `(name_key, postcode)` so the
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* creation — see `AGENTS.md` (inline-SQL → Kysely).
|
|
50
|
+
* Create the side-index — a clustered `WITHOUT ROWID` B-tree on `(name_key, postcode)` so the resolve is a single exact
|
|
51
|
+
* probe. Idempotent (`IF NOT EXISTS`); pass a {@link DatabaseClient} (or any `Kysely`) over the candidate DB. The
|
|
52
|
+
* Kysely schema-builder is the house idiom for table creation — see `AGENTS.md` (inline-SQL → Kysely).
|
|
56
53
|
*/
|
|
57
54
|
export declare function createPostalCityCandidateTable(db: Kysely<PostalCityCandidateDatabase>): Promise<void>;
|
|
58
55
|
//# sourceMappingURL=postal-city-candidate-schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postal-city-candidate-schema.d.ts","sourceRoot":"","sources":["../postal-city-candidate-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAO,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEzC;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACxC,sGAAsG;IACtG,QAAQ,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAA;IACd,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CACjB;AAED
|
|
1
|
+
{"version":3,"file":"postal-city-candidate-schema.d.ts","sourceRoot":"","sources":["../postal-city-candidate-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAO,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEzC;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACxC,sGAAsG;IACtG,QAAQ,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAA;IACd,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC3C,qBAAqB,EAAE,wBAAwB,CAAA;CAC/C;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,0BAA0B,CAAA;AAElE,wDAAwD;AACxD,eAAO,MAAM,6BAA6B,8EAOhC,CAAA;AAEV;;;;GAIG;AACH,wBAAsB,8BAA8B,CAAC,EAAE,EAAE,MAAM,CAAC,2BAA2B,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAc3G"}
|
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { sql } from "kysely";
|
|
21
21
|
/**
|
|
22
|
-
* The table name the lookup probes (existence-gated, so an old candidate.db without it is
|
|
23
|
-
* byte-stable).
|
|
22
|
+
* The table name the lookup probes (existence-gated, so an old candidate.db without it is byte-stable).
|
|
24
23
|
*/
|
|
25
24
|
export const POSTAL_CITY_CANDIDATE_TABLE = "postal_city_candidate";
|
|
26
25
|
/** Column order for the builder's positional INSERT. */
|
|
@@ -33,10 +32,9 @@ export const POSTAL_CITY_CANDIDATE_COLUMNS = [
|
|
|
33
32
|
"longitude",
|
|
34
33
|
];
|
|
35
34
|
/**
|
|
36
|
-
* Create the side-index — a clustered `WITHOUT ROWID` B-tree on `(name_key, postcode)` so the
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* creation — see `AGENTS.md` (inline-SQL → Kysely).
|
|
35
|
+
* Create the side-index — a clustered `WITHOUT ROWID` B-tree on `(name_key, postcode)` so the resolve is a single exact
|
|
36
|
+
* probe. Idempotent (`IF NOT EXISTS`); pass a {@link DatabaseClient} (or any `Kysely`) over the candidate DB. The
|
|
37
|
+
* Kysely schema-builder is the house idiom for table creation — see `AGENTS.md` (inline-SQL → Kysely).
|
|
40
38
|
*/
|
|
41
39
|
export async function createPostalCityCandidateTable(db) {
|
|
42
40
|
await db.schema
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postal-city-candidate-schema.js","sourceRoot":"","sources":["../postal-city-candidate-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,GAAG,EAAe,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"postal-city-candidate-schema.js","sourceRoot":"","sources":["../postal-city-candidate-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,GAAG,EAAe,MAAM,QAAQ,CAAA;AA0BzC;;GAEG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,uBAAuB,CAAA;AAElE,wDAAwD;AACxD,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC5C,UAAU;IACV,UAAU;IACV,QAAQ;IACR,MAAM;IACN,UAAU;IACV,WAAW;CACF,CAAA;AAEV;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,EAAuC;IAC3F,MAAM,EAAE,CAAC,MAAM;SACb,WAAW,CAAC,2BAA2B,CAAC;SACxC,WAAW,EAAE;SACb,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACjD,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACjD,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAClD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAC7C,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACjD,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAClD,uBAAuB,CAAC,0BAA0B,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC9E,0FAA0F;SACzF,SAAS,CAAC,GAAG,CAAA,eAAe,CAAC;SAC7B,OAAO,EAAE,CAAA;AACZ,CAAC"}
|
|
@@ -13,21 +13,20 @@
|
|
|
13
13
|
* anchor only needs "does this string exist as a postcode, in which countries, near where". A
|
|
14
14
|
* future WASM build swaps this for an FST-backed resolver behind the same `lookup()` seam.
|
|
15
15
|
*
|
|
16
|
-
* Why multiple shards instead of the multi-shard `
|
|
16
|
+
* Why multiple shards instead of the multi-shard `WOFSqlitePlaceLookup`: that resolver routes a
|
|
17
17
|
* query to ONE shard by placetype, but every postcode shard shares `placetype='postalcode'`, so a
|
|
18
18
|
* single query could only ever hit one country's shard. The anchor needs the union across
|
|
19
19
|
* countries to build its country posterior, so it queries each shard directly.
|
|
20
20
|
*/
|
|
21
21
|
/**
|
|
22
|
-
* A gazetteer hit. `lat`/`lon` of 0 means the postcode is known but has no centroid (no admin
|
|
23
|
-
* parent).
|
|
22
|
+
* A gazetteer hit. `lat`/`lon` of 0 means the postcode is known but has no centroid (no admin parent).
|
|
24
23
|
*/
|
|
25
24
|
export interface PostcodePlace {
|
|
26
25
|
country: string;
|
|
27
26
|
lat: number;
|
|
28
27
|
lon: number;
|
|
29
28
|
}
|
|
30
|
-
export declare class
|
|
29
|
+
export declare class WOFPostcodeLookup {
|
|
31
30
|
#private;
|
|
32
31
|
/** Open each shard read-only and prepare its exact-match statement. */
|
|
33
32
|
constructor(dbPaths: readonly string[]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postcode-point-lookup.d.ts","sourceRoot":"","sources":["../postcode-point-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH
|
|
1
|
+
{"version":3,"file":"postcode-point-lookup.d.ts","sourceRoot":"","sources":["../postcode-point-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACX;AAKD,qBAAa,iBAAiB;;IAI7B,uEAAuE;gBAC3D,OAAO,EAAE,SAAS,MAAM,EAAE;IAKtC,sEAAsE;IACtE,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE;IAYzC,KAAK,IAAI,IAAI;CAGb"}
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
* anchor only needs "does this string exist as a postcode, in which countries, near where". A
|
|
14
14
|
* future WASM build swaps this for an FST-backed resolver behind the same `lookup()` seam.
|
|
15
15
|
*
|
|
16
|
-
* Why multiple shards instead of the multi-shard `
|
|
16
|
+
* Why multiple shards instead of the multi-shard `WOFSqlitePlaceLookup`: that resolver routes a
|
|
17
17
|
* query to ONE shard by placetype, but every postcode shard shares `placetype='postalcode'`, so a
|
|
18
18
|
* single query could only ever hit one country's shard. The anchor needs the union across
|
|
19
19
|
* countries to build its country posterior, so it queries each shard directly.
|
|
20
20
|
*/
|
|
21
21
|
import { DatabaseSync } from "node:sqlite";
|
|
22
22
|
const LOOKUP_SQL = "SELECT country, latitude AS lat, longitude AS lon FROM spr WHERE name = ? AND placetype = 'postalcode' AND is_current != 0";
|
|
23
|
-
export class
|
|
23
|
+
export class WOFPostcodeLookup {
|
|
24
24
|
#dbs;
|
|
25
25
|
#stmts;
|
|
26
26
|
/** Open each shard read-only and prepare its exact-match statement. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postcode-point-lookup.js","sourceRoot":"","sources":["../postcode-point-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"postcode-point-lookup.js","sourceRoot":"","sources":["../postcode-point-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAW1C,MAAM,UAAU,GACf,4HAA4H,CAAA;AAE7H,MAAM,OAAO,iBAAiB;IACpB,IAAI,CAAgB;IACpB,MAAM,CAAuC;IAEtD,uEAAuE;IACvE,YAAY,OAA0B;QACrC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACvE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED,sEAAsE;IACtE,MAAM,CAAC,QAAgB;QACtB,MAAM,GAAG,GAAoB,EAAE,CAAA;QAE/B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACvF,CAAC;QACF,CAAC;QAED,OAAO,GAAG,CAAA;IACX,CAAC;IAED,KAAK;QACJ,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI;YAAE,EAAE,CAAC,KAAK,EAAE,CAAA;IACvC,CAAC;CACD"}
|
package/out/reverse.d.ts
CHANGED
|
@@ -28,69 +28,66 @@
|
|
|
28
28
|
* says so per result rather than pretending.
|
|
29
29
|
*/
|
|
30
30
|
import { DatabaseSync } from "node:sqlite";
|
|
31
|
-
import type { PlaceCandidate,
|
|
31
|
+
import type { PlaceCandidate, WOFPlacetype } from "./types.js";
|
|
32
32
|
/**
|
|
33
33
|
* How the deepest returned place was confirmed:
|
|
34
34
|
*
|
|
35
35
|
* - `"polygon"` — the point ray-cast INSIDE the place's real (DP-simplified) admin boundary.
|
|
36
|
-
* - `"approximate"` — the place has no polygon on record; it won by nearest-centroid among the
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* - `"approximate"` — the place has no polygon on record; it won by nearest-centroid among the candidates whose bbox (or
|
|
37
|
+
* parent) contains the point. The same honesty convention as the demo's approximate circles — country-dependent data
|
|
38
|
+
* reality, surfaced instead of hidden.
|
|
39
39
|
*/
|
|
40
40
|
export type ContainmentKind = "polygon" | "approximate";
|
|
41
41
|
export interface ReverseGeocodeResult {
|
|
42
42
|
/**
|
|
43
|
-
* The containment chain, DEEPEST-FIRST (`[0]` is the winning place, then its ancestors up to
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* The containment chain, DEEPEST-FIRST (`[0]` is the winning place, then its ancestors up to country) — the same tree
|
|
44
|
+
* shape forward resolution attaches via `includeAncestors`. Empty when no candidate's bbox contains the point (open
|
|
45
|
+
* ocean, or outside the gazetteer's coverage).
|
|
46
46
|
*/
|
|
47
47
|
hierarchy: PlaceCandidate[];
|
|
48
48
|
/** Containment kind of the DEEPEST place in `hierarchy` (see {@link ContainmentKind}). */
|
|
49
49
|
containment: ContainmentKind;
|
|
50
50
|
}
|
|
51
|
-
export interface
|
|
51
|
+
export interface WOFReverseGeocoderOpts {
|
|
52
52
|
/**
|
|
53
|
-
* Path to the admin gazetteer DB (e.g. `admin-global-priority.db`) — must carry `spr`,
|
|
54
|
-
*
|
|
55
|
-
* exclusive with `adminDatabase`.
|
|
53
|
+
* Path to the admin gazetteer DB (e.g. `admin-global-priority.db`) — must carry `spr`, `ancestors`, and the
|
|
54
|
+
* package-built `place_bbox` R*Tree (`mailwoman-wof-build-fts`). Mutually exclusive with `adminDatabase`.
|
|
56
55
|
*/
|
|
57
|
-
|
|
56
|
+
adminDBPath?: string;
|
|
58
57
|
/** Pre-opened admin DB — primarily for tests against an inline fixture. */
|
|
59
58
|
adminDatabase?: DatabaseSync;
|
|
60
59
|
/**
|
|
61
|
-
* Path to the polygon sidecar DB (`wof-polygons.db`, table `polygons(id, geom)`). OPTIONAL —
|
|
62
|
-
*
|
|
63
|
-
* exclusive with `polygonDatabase`.
|
|
60
|
+
* Path to the polygon sidecar DB (`wof-polygons.db`, table `polygons(id, geom)`). OPTIONAL — without it every result
|
|
61
|
+
* is `containment: "approximate"` (centroid-only mode). Mutually exclusive with `polygonDatabase`.
|
|
64
62
|
*/
|
|
65
|
-
|
|
63
|
+
polygonDBPath?: string;
|
|
66
64
|
/** Pre-opened polygon DB — primarily for tests. */
|
|
67
65
|
polygonDatabase?: DatabaseSync;
|
|
68
66
|
}
|
|
69
67
|
export interface ReverseGeocodeOpts {
|
|
70
68
|
/**
|
|
71
|
-
* Restrict the hierarchy to these placetypes (both the bbox candidates and the descent tiers).
|
|
72
|
-
*
|
|
73
|
-
* to skip the neighbourhood grain.
|
|
69
|
+
* Restrict the hierarchy to these placetypes (both the bbox candidates and the descent tiers). Default: every admin
|
|
70
|
+
* placetype the gazetteer carries. E.g. `["region", "county", "locality"]` to skip the neighbourhood grain.
|
|
74
71
|
*/
|
|
75
|
-
placetypes?:
|
|
72
|
+
placetypes?: WOFPlacetype[];
|
|
76
73
|
/**
|
|
77
|
-
* Cap on the bbox candidate fetch. Default 128 — comfortably covers a dense metro (the most
|
|
78
|
-
*
|
|
74
|
+
* Cap on the bbox candidate fetch. Default 128 — comfortably covers a dense metro (the most bbox-overlapping point
|
|
75
|
+
* we've measured is a few dozen neighbourhoods + the admin chain).
|
|
79
76
|
*/
|
|
80
77
|
maxCandidates?: number;
|
|
81
78
|
/**
|
|
82
|
-
* Approximate (nearest-centroid) steps further than this from the query point are not taken —
|
|
83
|
-
*
|
|
84
|
-
*
|
|
79
|
+
* Approximate (nearest-centroid) steps further than this from the query point are not taken — keeps a sparse
|
|
80
|
+
* gazetteer from "refining" to a far-away sibling. Polygon-confirmed steps ignore it (containment is exact regardless
|
|
81
|
+
* of centroid distance). Default 25 km.
|
|
85
82
|
*/
|
|
86
83
|
maxApproximateKm?: number;
|
|
87
84
|
}
|
|
88
|
-
export declare class
|
|
85
|
+
export declare class WOFReverseGeocoder implements Disposable {
|
|
89
86
|
#private;
|
|
90
|
-
constructor(opts:
|
|
87
|
+
constructor(opts: WOFReverseGeocoderOpts);
|
|
91
88
|
/**
|
|
92
|
-
* Resolve a WGS-84 point to its containing admin hierarchy. Async for symmetry with
|
|
93
|
-
*
|
|
89
|
+
* Resolve a WGS-84 point to its containing admin hierarchy. Async for symmetry with `PlaceLookup.findPlace` (the work
|
|
90
|
+
* is sync `node:sqlite` underneath — same convention).
|
|
94
91
|
*/
|
|
95
92
|
reverseGeocode(lat: number, lon: number, opts?: ReverseGeocodeOpts): Promise<ReverseGeocodeResult>;
|
|
96
93
|
close(): void;
|
package/out/reverse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reverse.d.ts","sourceRoot":"","sources":["../reverse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAK1C,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,aAAa,CAAA;AAEvD,MAAM,WAAW,oBAAoB;IACpC;;;;OAIG;IACH,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,0FAA0F;IAC1F,WAAW,EAAE,eAAe,CAAA;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACtC
|
|
1
|
+
{"version":3,"file":"reverse.d.ts","sourceRoot":"","sources":["../reverse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAK1C,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,aAAa,CAAA;AAEvD,MAAM,WAAW,oBAAoB;IACpC;;;;OAIG;IACH,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,0FAA0F;IAC1F,WAAW,EAAE,eAAe,CAAA;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,YAAY,CAAA;IAC5B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mDAAmD;IACnD,eAAe,CAAC,EAAE,YAAY,CAAA;CAC9B;AAED,MAAM,WAAW,kBAAkB;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,YAAY,EAAE,CAAA;IAC3B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CACzB;AA8CD,qBAAa,kBAAmB,YAAW,UAAU;;gBAcxC,IAAI,EAAE,sBAAsB;IA8CxC;;;OAGG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA6M5G,KAAK,IAAI,IAAI;IAMb,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGxB"}
|
package/out/reverse.js
CHANGED
|
@@ -34,9 +34,8 @@ import { geometryContains, haversineKm } from "./geo.js";
|
|
|
34
34
|
const DEFAULT_MAX_CANDIDATES = 128;
|
|
35
35
|
const DEFAULT_MAX_APPROXIMATE_KM = 25;
|
|
36
36
|
/**
|
|
37
|
-
* The tier ladder for the approximate descent, coarsest-first. Each tier is attempted among the
|
|
38
|
-
*
|
|
39
|
-
* jump straight to locality).
|
|
37
|
+
* The tier ladder for the approximate descent, coarsest-first. Each tier is attempted among the CURRENT winner's
|
|
38
|
+
* descendants; a tier with no rows is skipped (e.g. counties without localadmins jump straight to locality).
|
|
40
39
|
*/
|
|
41
40
|
const DESCENT_TIERS = [
|
|
42
41
|
"county",
|
|
@@ -61,41 +60,41 @@ function toPlaceCandidate(row, distanceKm) {
|
|
|
61
60
|
c.distanceKm = distanceKm;
|
|
62
61
|
return c;
|
|
63
62
|
}
|
|
64
|
-
export class
|
|
63
|
+
export class WOFReverseGeocoder {
|
|
65
64
|
#admin;
|
|
66
65
|
#ownsAdmin;
|
|
67
66
|
#polygons;
|
|
68
67
|
#ownsPolygons;
|
|
69
68
|
/**
|
|
70
|
-
* Parsed-geometry cache. Reverse queries cluster geographically (an eval run hits the same ~15
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
69
|
+
* Parsed-geometry cache. Reverse queries cluster geographically (an eval run hits the same ~15 county polygons 1400
|
|
70
|
+
* times), so caching the JSON.parse pays for itself immediately. Bounded — cleared wholesale at the cap rather than
|
|
71
|
+
* LRU-tracked; the polygons are DP-simplified and small, the cap exists only to keep a long-lived server process
|
|
72
|
+
* honest.
|
|
74
73
|
*/
|
|
75
74
|
#geometryCache = new Map();
|
|
76
75
|
static #GEOMETRY_CACHE_CAP = 4096;
|
|
77
76
|
constructor(opts) {
|
|
78
|
-
if (opts.adminDatabase && opts.
|
|
79
|
-
throw new Error("
|
|
77
|
+
if (opts.adminDatabase && opts.adminDBPath) {
|
|
78
|
+
throw new Error("WOFReverseGeocoder: pass either `adminDatabase` or `adminDBPath`, not both");
|
|
80
79
|
}
|
|
81
|
-
if (!opts.adminDatabase && !opts.
|
|
82
|
-
throw new Error("
|
|
80
|
+
if (!opts.adminDatabase && !opts.adminDBPath) {
|
|
81
|
+
throw new Error("WOFReverseGeocoder: one of `adminDatabase` or `adminDBPath` is required");
|
|
83
82
|
}
|
|
84
|
-
if (opts.polygonDatabase && opts.
|
|
85
|
-
throw new Error("
|
|
83
|
+
if (opts.polygonDatabase && opts.polygonDBPath) {
|
|
84
|
+
throw new Error("WOFReverseGeocoder: pass either `polygonDatabase` or `polygonDBPath`, not both");
|
|
86
85
|
}
|
|
87
|
-
this.#admin = opts.adminDatabase ?? new DatabaseSync(opts.
|
|
86
|
+
this.#admin = opts.adminDatabase ?? new DatabaseSync(opts.adminDBPath, { readOnly: true });
|
|
88
87
|
this.#ownsAdmin = !opts.adminDatabase;
|
|
89
88
|
this.#polygons =
|
|
90
|
-
opts.polygonDatabase ?? (opts.
|
|
91
|
-
this.#ownsPolygons = !opts.polygonDatabase && Boolean(opts.
|
|
89
|
+
opts.polygonDatabase ?? (opts.polygonDBPath ? new DatabaseSync(opts.polygonDBPath, { readOnly: true }) : null);
|
|
90
|
+
this.#ownsPolygons = !opts.polygonDatabase && Boolean(opts.polygonDBPath);
|
|
92
91
|
// Fail loudly up front — the R*Tree is a build artifact, not part of the upstream WOF
|
|
93
92
|
// distribution, and a missing index would otherwise surface as an opaque SQL error per query.
|
|
94
93
|
const hasBbox = this.#admin
|
|
95
94
|
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
96
95
|
.get(PLACE_BBOX_TABLE);
|
|
97
96
|
if (!hasBbox) {
|
|
98
|
-
throw new Error(`
|
|
97
|
+
throw new Error(`WOFReverseGeocoder: the admin DB has no \`${PLACE_BBOX_TABLE}\` R*Tree. Build it with ` +
|
|
99
98
|
"`mailwoman-wof-build-fts <path-to-wof.db>` (see resolver-wof-sqlite/README.md).");
|
|
100
99
|
}
|
|
101
100
|
if (this.#polygons) {
|
|
@@ -103,18 +102,18 @@ export class WofReverseGeocoder {
|
|
|
103
102
|
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'polygons'`)
|
|
104
103
|
.get();
|
|
105
104
|
if (!hasPolygons) {
|
|
106
|
-
throw new Error("
|
|
105
|
+
throw new Error("WOFReverseGeocoder: the polygon DB has no `polygons` table. Expected a `wof-polygons.db` " +
|
|
107
106
|
"built by scripts/build-wof-polygons.mjs.");
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
/**
|
|
112
|
-
* Resolve a WGS-84 point to its containing admin hierarchy. Async for symmetry with
|
|
113
|
-
*
|
|
111
|
+
* Resolve a WGS-84 point to its containing admin hierarchy. Async for symmetry with `PlaceLookup.findPlace` (the work
|
|
112
|
+
* is sync `node:sqlite` underneath — same convention).
|
|
114
113
|
*/
|
|
115
114
|
async reverseGeocode(lat, lon, opts = {}) {
|
|
116
115
|
if (!Number.isFinite(lat) || !Number.isFinite(lon) || Math.abs(lat) > 90 || Math.abs(lon) > 180) {
|
|
117
|
-
throw new RangeError(`
|
|
116
|
+
throw new RangeError(`WOFReverseGeocoder.reverseGeocode: (${lat}, ${lon}) is not a WGS-84 coordinate`);
|
|
118
117
|
}
|
|
119
118
|
const maxApproximateKm = opts.maxApproximateKm ?? DEFAULT_MAX_APPROXIMATE_KM;
|
|
120
119
|
const candidates = this.#bboxCandidates(lat, lon, opts);
|
|
@@ -188,22 +187,22 @@ export class WofReverseGeocoder {
|
|
|
188
187
|
// Hierarchy assembly via the shared ancestor walk. If the descent crossed an ancestry gap
|
|
189
188
|
// (the deepest place's recorded lineage misses the PIP root), merge the root's own chain so
|
|
190
189
|
// region/country are always present when a polygon confirmed them.
|
|
191
|
-
const
|
|
192
|
-
|
|
190
|
+
const byID = new Map();
|
|
191
|
+
byID.set(current.id, toPlaceCandidate(current, currentDistanceKm));
|
|
193
192
|
for (const a of ancestorLineage(this.#admin, current.id)) {
|
|
194
|
-
if (!
|
|
195
|
-
|
|
193
|
+
if (!byID.has(a.id)) {
|
|
194
|
+
byID.set(a.id, { ...a, placetype: a.placetype, country: a.country ?? "", score: 0 });
|
|
196
195
|
}
|
|
197
196
|
}
|
|
198
|
-
if (!
|
|
199
|
-
|
|
197
|
+
if (!byID.has(winner.id)) {
|
|
198
|
+
byID.set(winner.id, toPlaceCandidate(winner));
|
|
200
199
|
for (const a of ancestorLineage(this.#admin, winner.id)) {
|
|
201
|
-
if (!
|
|
202
|
-
|
|
200
|
+
if (!byID.has(a.id)) {
|
|
201
|
+
byID.set(a.id, { ...a, placetype: a.placetype, country: a.country ?? "", score: 0 });
|
|
203
202
|
}
|
|
204
203
|
}
|
|
205
204
|
}
|
|
206
|
-
const hierarchy = [...
|
|
205
|
+
const hierarchy = [...byID.values()];
|
|
207
206
|
if (opts.placetypes) {
|
|
208
207
|
const allowed = new Set(opts.placetypes);
|
|
209
208
|
for (let i = hierarchy.length - 1; i >= 0; i--) {
|
|
@@ -240,12 +239,11 @@ export class WofReverseGeocoder {
|
|
|
240
239
|
.all(...params);
|
|
241
240
|
}
|
|
242
241
|
/**
|
|
243
|
-
* Descendants of `
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* the caller, and only to centroid-fallback steps).
|
|
242
|
+
* Descendants of `parentID` at one placetype tier, pre-filtered to a centroid window around the query point (a
|
|
243
|
+
* generous 4× the approximate cap — polygon-holding children may legitimately have far centroids, e.g. a sprawling
|
|
244
|
+
* consolidated city; the precise cap is applied per-candidate in the caller, and only to centroid-fallback steps).
|
|
247
245
|
*/
|
|
248
|
-
#descendants(
|
|
246
|
+
#descendants(parentID, placetype, lat, lon, maxApproximateKm) {
|
|
249
247
|
const windowDeg = (maxApproximateKm * 4) / 111;
|
|
250
248
|
return this.#admin
|
|
251
249
|
.prepare(`SELECT s.id AS id, s.name AS name, s.placetype AS placetype, s.country AS country,
|
|
@@ -253,7 +251,7 @@ export class WofReverseGeocoder {
|
|
|
253
251
|
FROM ancestors a JOIN spr s ON s.id = a.id
|
|
254
252
|
WHERE a.ancestor_id = ? AND s.placetype = ? AND s.is_current != 0 AND s.is_deprecated = 0
|
|
255
253
|
AND s.latitude BETWEEN ? AND ? AND s.longitude BETWEEN ? AND ?`)
|
|
256
|
-
.all(
|
|
254
|
+
.all(parentID, placetype, lat - windowDeg, lat + windowDeg, lon - windowDeg, lon + windowDeg);
|
|
257
255
|
}
|
|
258
256
|
/** Parsed GeoJSON geometry for a WOF id, or null when absent / unparseable / no polygon DB. */
|
|
259
257
|
#geometry(id) {
|
|
@@ -262,7 +260,7 @@ export class WofReverseGeocoder {
|
|
|
262
260
|
const cached = this.#geometryCache.get(id);
|
|
263
261
|
if (cached !== undefined)
|
|
264
262
|
return cached;
|
|
265
|
-
if (this.#geometryCache.size >=
|
|
263
|
+
if (this.#geometryCache.size >= WOFReverseGeocoder.#GEOMETRY_CACHE_CAP)
|
|
266
264
|
this.#geometryCache.clear();
|
|
267
265
|
const row = this.#polygons.prepare(`SELECT geom FROM polygons WHERE id = ?`).get(id);
|
|
268
266
|
let geometry = null;
|