@mailwoman/resolver-wof-sqlite 10.0.0 → 10.2.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.
Files changed (477) hide show
  1. package/README.md +9 -8
  2. package/lib/address/point/interpolation.ts +45 -39
  3. package/lib/address/point/schema.ts +17 -40
  4. package/lib/address/point.ts +316 -0
  5. package/lib/{address/index.ts → address.ts} +1 -1
  6. package/lib/ancestry/backfill.ts +20 -86
  7. package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
  8. package/lib/build/candidate.ts +122 -174
  9. package/lib/build/population-corrections.ts +64 -0
  10. package/lib/build/slim.ts +44 -114
  11. package/lib/candidate/alias-bags.ts +16 -17
  12. package/lib/candidate/ancestors/schema.ts +77 -61
  13. package/lib/candidate/ancestors/sidecar.ts +15 -22
  14. package/lib/candidate/country-display-names.ts +6 -20
  15. package/lib/candidate/extract-fold.ts +10 -55
  16. package/lib/candidate/fts.ts +5 -23
  17. package/lib/candidate/importance.ts +65 -68
  18. package/lib/candidate/lookup.ts +124 -415
  19. package/lib/candidate/name-roles.ts +15 -33
  20. package/lib/candidate/own-name.ts +35 -32
  21. package/lib/candidate/place-attrs.ts +20 -13
  22. package/lib/candidate/schema.ts +29 -65
  23. package/lib/candidate/scoring.ts +54 -93
  24. package/lib/capital-schema.ts +21 -17
  25. package/lib/capitals.ts +29 -27
  26. package/lib/coincident-roles.ts +85 -55
  27. package/lib/convention/schema.ts +15 -16
  28. package/lib/convention.ts +144 -0
  29. package/lib/coverage-manifest-schema.ts +40 -45
  30. package/lib/currency-backfill.ts +34 -79
  31. package/lib/env.ts +1 -1
  32. package/lib/exact-match.ts +32 -21
  33. package/lib/extracts.ts +97 -74
  34. package/lib/fst/autocomplete.ts +54 -37
  35. package/lib/fst/builder.ts +37 -41
  36. package/lib/fst/deserialize-web.ts +16 -17
  37. package/lib/fst/format.ts +41 -23
  38. package/lib/fst/freshness.ts +43 -120
  39. package/lib/fst/matcher.ts +7 -5
  40. package/lib/fst/serialize.ts +54 -74
  41. package/lib/fst/types.ts +78 -75
  42. package/lib/{fst/index.ts → fst.ts} +1 -0
  43. package/lib/fts/query.ts +5 -36
  44. package/lib/fts.ts +280 -0
  45. package/lib/geonames/aliases.ts +73 -144
  46. package/lib/geonames/postal.ts +162 -71
  47. package/lib/index.ts +9 -8
  48. package/lib/interpolation.ts +48 -100
  49. package/lib/lookup.ts +155 -270
  50. package/lib/name-score.ts +9 -7
  51. package/lib/nsul/lookup.ts +37 -54
  52. package/lib/nsul/schema.ts +16 -43
  53. package/lib/paths.ts +159 -0
  54. package/lib/place-importance-schema.ts +62 -169
  55. package/lib/poi/lookup.ts +104 -121
  56. package/lib/poi/schema.ts +42 -26
  57. package/lib/polygon-schema.ts +10 -6
  58. package/lib/postal/city/alias/lookup.ts +22 -40
  59. package/lib/postal/city/alias/schema.ts +14 -10
  60. package/lib/postal/city/candidate-schema.ts +26 -20
  61. package/lib/postcode-point-lookup.ts +8 -27
  62. package/lib/primary-preference.ts +46 -126
  63. package/lib/proximity-rerank.ts +44 -33
  64. package/lib/ranking-weights.ts +105 -78
  65. package/lib/region-keys.ts +70 -42
  66. package/lib/reverse.ts +149 -86
  67. package/lib/schema.ts +73 -50
  68. package/lib/search-fetch.ts +90 -68
  69. package/lib/sqlite/convention-source.ts +15 -23
  70. package/lib/sqlite/utils.ts +19 -22
  71. package/lib/street/centroid/schema.ts +37 -42
  72. package/lib/street/{centroid/index.ts → centroid.ts} +16 -25
  73. package/lib/street/morphology/fst/builder.ts +60 -42
  74. package/lib/street/morphology/fst/loader.ts +40 -32
  75. package/lib/street/name-lookup.ts +45 -41
  76. package/lib/street/normalize.ts +88 -219
  77. package/lib/street/segment-schema.ts +116 -43
  78. package/lib/{street/index.ts → street.ts} +1 -1
  79. package/lib/types.ts +109 -150
  80. package/lib/unified-schema.ts +29 -29
  81. package/lib/uprn/existence.ts +30 -19
  82. package/lib/uprn/lookup.ts +29 -68
  83. package/lib/uprn/schema.ts +18 -40
  84. package/lib/weights-overlay-linker.ts +236 -252
  85. package/out/address/point/interpolation.d.ts +20 -23
  86. package/out/address/point/interpolation.d.ts.map +1 -1
  87. package/out/address/point/interpolation.js +42 -39
  88. package/out/address/point/interpolation.js.map +1 -1
  89. package/out/address/point/schema.d.ts +12 -35
  90. package/out/address/point/schema.d.ts.map +1 -1
  91. package/out/address/point/schema.js +3 -19
  92. package/out/address/point/schema.js.map +1 -1
  93. package/out/address/point.d.ts +57 -0
  94. package/out/address/point.d.ts.map +1 -0
  95. package/out/address/point.js +206 -0
  96. package/out/address/point.js.map +1 -0
  97. package/out/{address/index.js → address.d.ts} +2 -2
  98. package/out/address.d.ts.map +1 -0
  99. package/out/{address/index.d.ts → address.js} +2 -2
  100. package/out/address.js.map +1 -0
  101. package/out/ancestry/backfill.d.ts +11 -64
  102. package/out/ancestry/backfill.d.ts.map +1 -1
  103. package/out/ancestry/backfill.js +14 -74
  104. package/out/ancestry/backfill.js.map +1 -1
  105. package/out/ancestry.d.ts +49 -0
  106. package/out/ancestry.d.ts.map +1 -0
  107. package/out/ancestry.js +59 -0
  108. package/out/ancestry.js.map +1 -0
  109. package/out/build/candidate.d.ts +82 -112
  110. package/out/build/candidate.d.ts.map +1 -1
  111. package/out/build/candidate.js +17 -100
  112. package/out/build/candidate.js.map +1 -1
  113. package/out/build/population-corrections.d.ts +52 -0
  114. package/out/build/population-corrections.d.ts.map +1 -0
  115. package/out/build/population-corrections.js +40 -0
  116. package/out/build/population-corrections.js.map +1 -0
  117. package/out/build/slim.d.ts +8 -44
  118. package/out/build/slim.d.ts.map +1 -1
  119. package/out/build/slim.js +33 -94
  120. package/out/build/slim.js.map +1 -1
  121. package/out/candidate/alias-bags.d.ts +6 -6
  122. package/out/candidate/alias-bags.d.ts.map +1 -1
  123. package/out/candidate/alias-bags.js +16 -17
  124. package/out/candidate/alias-bags.js.map +1 -1
  125. package/out/candidate/ancestors/schema.d.ts +73 -57
  126. package/out/candidate/ancestors/schema.d.ts.map +1 -1
  127. package/out/candidate/ancestors/schema.js +56 -48
  128. package/out/candidate/ancestors/schema.js.map +1 -1
  129. package/out/candidate/ancestors/sidecar.d.ts +3 -8
  130. package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
  131. package/out/candidate/ancestors/sidecar.js +15 -22
  132. package/out/candidate/ancestors/sidecar.js.map +1 -1
  133. package/out/candidate/country-display-names.d.ts +5 -15
  134. package/out/candidate/country-display-names.d.ts.map +1 -1
  135. package/out/candidate/country-display-names.js +6 -20
  136. package/out/candidate/country-display-names.js.map +1 -1
  137. package/out/candidate/extract-fold.d.ts +7 -15
  138. package/out/candidate/extract-fold.d.ts.map +1 -1
  139. package/out/candidate/extract-fold.js +5 -47
  140. package/out/candidate/extract-fold.js.map +1 -1
  141. package/out/candidate/fts.d.ts +5 -23
  142. package/out/candidate/fts.d.ts.map +1 -1
  143. package/out/candidate/fts.js +5 -23
  144. package/out/candidate/fts.js.map +1 -1
  145. package/out/candidate/importance.d.ts +60 -65
  146. package/out/candidate/importance.d.ts.map +1 -1
  147. package/out/candidate/importance.js +56 -63
  148. package/out/candidate/importance.js.map +1 -1
  149. package/out/candidate/lookup.d.ts +18 -55
  150. package/out/candidate/lookup.d.ts.map +1 -1
  151. package/out/candidate/lookup.js +82 -385
  152. package/out/candidate/lookup.js.map +1 -1
  153. package/out/candidate/name-roles.d.ts +9 -21
  154. package/out/candidate/name-roles.d.ts.map +1 -1
  155. package/out/candidate/name-roles.js +15 -33
  156. package/out/candidate/name-roles.js.map +1 -1
  157. package/out/candidate/own-name.d.ts +23 -25
  158. package/out/candidate/own-name.d.ts.map +1 -1
  159. package/out/candidate/own-name.js +35 -32
  160. package/out/candidate/own-name.js.map +1 -1
  161. package/out/candidate/place-attrs.d.ts +20 -13
  162. package/out/candidate/place-attrs.d.ts.map +1 -1
  163. package/out/candidate/place-attrs.js +4 -5
  164. package/out/candidate/place-attrs.js.map +1 -1
  165. package/out/candidate/schema.d.ts +27 -62
  166. package/out/candidate/schema.d.ts.map +1 -1
  167. package/out/candidate/schema.js +10 -22
  168. package/out/candidate/schema.js.map +1 -1
  169. package/out/candidate/scoring.d.ts +7 -8
  170. package/out/candidate/scoring.d.ts.map +1 -1
  171. package/out/candidate/scoring.js +52 -89
  172. package/out/candidate/scoring.js.map +1 -1
  173. package/out/capital-schema.d.ts +21 -17
  174. package/out/capital-schema.d.ts.map +1 -1
  175. package/out/capital-schema.js +17 -15
  176. package/out/capital-schema.js.map +1 -1
  177. package/out/capitals.d.ts +27 -25
  178. package/out/capitals.d.ts.map +1 -1
  179. package/out/capitals.js +23 -21
  180. package/out/capitals.js.map +1 -1
  181. package/out/coincident-roles.d.ts +60 -43
  182. package/out/coincident-roles.d.ts.map +1 -1
  183. package/out/coincident-roles.js +72 -50
  184. package/out/coincident-roles.js.map +1 -1
  185. package/out/convention/schema.d.ts +14 -14
  186. package/out/convention/schema.d.ts.map +1 -1
  187. package/out/convention/schema.js +6 -4
  188. package/out/convention/schema.js.map +1 -1
  189. package/out/convention.d.ts +97 -0
  190. package/out/convention.d.ts.map +1 -0
  191. package/out/convention.js +81 -0
  192. package/out/convention.js.map +1 -0
  193. package/out/coverage-manifest-schema.d.ts +34 -39
  194. package/out/coverage-manifest-schema.d.ts.map +1 -1
  195. package/out/coverage-manifest-schema.js +25 -34
  196. package/out/coverage-manifest-schema.js.map +1 -1
  197. package/out/currency-backfill.d.ts +16 -38
  198. package/out/currency-backfill.d.ts.map +1 -1
  199. package/out/currency-backfill.js +19 -46
  200. package/out/currency-backfill.js.map +1 -1
  201. package/out/env.d.ts +1 -1
  202. package/out/env.js +1 -1
  203. package/out/exact-match.d.ts +19 -10
  204. package/out/exact-match.d.ts.map +1 -1
  205. package/out/exact-match.js +32 -21
  206. package/out/exact-match.js.map +1 -1
  207. package/out/extracts.d.ts +66 -55
  208. package/out/extracts.d.ts.map +1 -1
  209. package/out/extracts.js +63 -55
  210. package/out/extracts.js.map +1 -1
  211. package/out/fst/autocomplete.d.ts +29 -21
  212. package/out/fst/autocomplete.d.ts.map +1 -1
  213. package/out/fst/autocomplete.js +39 -28
  214. package/out/fst/autocomplete.js.map +1 -1
  215. package/out/fst/builder.d.ts.map +1 -1
  216. package/out/fst/builder.js +37 -41
  217. package/out/fst/builder.js.map +1 -1
  218. package/out/fst/deserialize-web.d.ts +4 -4
  219. package/out/fst/deserialize-web.d.ts.map +1 -1
  220. package/out/fst/deserialize-web.js +15 -16
  221. package/out/fst/deserialize-web.js.map +1 -1
  222. package/out/fst/format.d.ts +41 -23
  223. package/out/fst/format.d.ts.map +1 -1
  224. package/out/fst/format.js +41 -23
  225. package/out/fst/format.js.map +1 -1
  226. package/out/fst/freshness.d.ts +19 -81
  227. package/out/fst/freshness.d.ts.map +1 -1
  228. package/out/fst/freshness.js +31 -94
  229. package/out/fst/freshness.js.map +1 -1
  230. package/out/fst/matcher.d.ts +5 -4
  231. package/out/fst/matcher.d.ts.map +1 -1
  232. package/out/fst/matcher.js +2 -2
  233. package/out/fst/matcher.js.map +1 -1
  234. package/out/fst/serialize.d.ts +28 -28
  235. package/out/fst/serialize.d.ts.map +1 -1
  236. package/out/fst/serialize.js +53 -71
  237. package/out/fst/serialize.js.map +1 -1
  238. package/out/fst/types.d.ts +78 -74
  239. package/out/fst/types.d.ts.map +1 -1
  240. package/out/fst/types.js +1 -4
  241. package/out/fst/types.js.map +1 -1
  242. package/out/{fst/index.js → fst.d.ts} +2 -1
  243. package/out/fst.d.ts.map +1 -0
  244. package/out/{fst/index.d.ts → fst.js} +2 -1
  245. package/out/fst.js.map +1 -0
  246. package/out/fts/query.d.ts +2 -27
  247. package/out/fts/query.d.ts.map +1 -1
  248. package/out/fts/query.js +5 -36
  249. package/out/fts/query.js.map +1 -1
  250. package/out/fts.d.ts +126 -0
  251. package/out/fts.d.ts.map +1 -0
  252. package/out/fts.js +188 -0
  253. package/out/fts.js.map +1 -0
  254. package/out/geonames/aliases.d.ts +26 -62
  255. package/out/geonames/aliases.d.ts.map +1 -1
  256. package/out/geonames/aliases.js +45 -117
  257. package/out/geonames/aliases.js.map +1 -1
  258. package/out/geonames/postal.d.ts +84 -44
  259. package/out/geonames/postal.d.ts.map +1 -1
  260. package/out/geonames/postal.js +115 -73
  261. package/out/geonames/postal.js.map +1 -1
  262. package/out/{geonames/index.js → geonames.d.ts} +1 -1
  263. package/out/geonames.d.ts.map +1 -0
  264. package/out/{geonames/index.d.ts → geonames.js} +1 -1
  265. package/out/geonames.js.map +1 -0
  266. package/out/index.d.ts +9 -8
  267. package/out/index.d.ts.map +1 -1
  268. package/out/index.js +6 -5
  269. package/out/index.js.map +1 -1
  270. package/out/interpolation.d.ts +18 -55
  271. package/out/interpolation.d.ts.map +1 -1
  272. package/out/interpolation.js +27 -69
  273. package/out/interpolation.js.map +1 -1
  274. package/out/lookup.d.ts +23 -51
  275. package/out/lookup.d.ts.map +1 -1
  276. package/out/lookup.js +136 -234
  277. package/out/lookup.js.map +1 -1
  278. package/out/name-score.d.ts +2 -2
  279. package/out/name-score.d.ts.map +1 -1
  280. package/out/name-score.js +9 -7
  281. package/out/name-score.js.map +1 -1
  282. package/out/nsul/lookup.d.ts +33 -34
  283. package/out/nsul/lookup.d.ts.map +1 -1
  284. package/out/nsul/lookup.js +26 -40
  285. package/out/nsul/lookup.js.map +1 -1
  286. package/out/nsul/schema.d.ts +16 -43
  287. package/out/nsul/schema.d.ts.map +1 -1
  288. package/out/nsul/schema.js +9 -35
  289. package/out/nsul/schema.js.map +1 -1
  290. package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
  291. package/out/nsul.d.ts.map +1 -0
  292. package/out/{nsul/index.js → nsul.js} +1 -1
  293. package/out/nsul.js.map +1 -0
  294. package/out/paths.d.ts +124 -0
  295. package/out/paths.d.ts.map +1 -0
  296. package/out/paths.js +104 -0
  297. package/out/paths.js.map +1 -0
  298. package/out/place-importance-schema.d.ts +51 -143
  299. package/out/place-importance-schema.d.ts.map +1 -1
  300. package/out/place-importance-schema.js +28 -143
  301. package/out/place-importance-schema.js.map +1 -1
  302. package/out/poi/lookup.d.ts +40 -47
  303. package/out/poi/lookup.d.ts.map +1 -1
  304. package/out/poi/lookup.js +69 -96
  305. package/out/poi/lookup.js.map +1 -1
  306. package/out/poi/schema.d.ts +41 -25
  307. package/out/poi/schema.d.ts.map +1 -1
  308. package/out/poi/schema.js +25 -17
  309. package/out/poi/schema.js.map +1 -1
  310. package/out/{poi/index.js → poi.d.ts} +1 -1
  311. package/out/poi.d.ts.map +1 -0
  312. package/out/{poi/index.d.ts → poi.js} +1 -1
  313. package/out/poi.js.map +1 -0
  314. package/out/polygon-schema.d.ts +10 -6
  315. package/out/polygon-schema.d.ts.map +1 -1
  316. package/out/polygon-schema.js +2 -2
  317. package/out/polygon-schema.js.map +1 -1
  318. package/out/postal/city/alias/lookup.d.ts +20 -23
  319. package/out/postal/city/alias/lookup.d.ts.map +1 -1
  320. package/out/postal/city/alias/lookup.js +17 -30
  321. package/out/postal/city/alias/lookup.js.map +1 -1
  322. package/out/postal/city/alias/schema.d.ts +14 -10
  323. package/out/postal/city/alias/schema.d.ts.map +1 -1
  324. package/out/postal/city/alias/schema.js +10 -8
  325. package/out/postal/city/alias/schema.js.map +1 -1
  326. package/out/postal/city/candidate-schema.d.ts +25 -19
  327. package/out/postal/city/candidate-schema.d.ts.map +1 -1
  328. package/out/postal/city/candidate-schema.js +21 -17
  329. package/out/postal/city/candidate-schema.js.map +1 -1
  330. package/out/{postal/index.d.ts → postal.d.ts} +1 -1
  331. package/out/postal.d.ts.map +1 -0
  332. package/out/{postal/index.js → postal.js} +1 -1
  333. package/out/postal.js.map +1 -0
  334. package/out/postcode-point-lookup.d.ts +6 -24
  335. package/out/postcode-point-lookup.d.ts.map +1 -1
  336. package/out/postcode-point-lookup.js +5 -17
  337. package/out/postcode-point-lookup.js.map +1 -1
  338. package/out/primary-preference.d.ts +38 -92
  339. package/out/primary-preference.d.ts.map +1 -1
  340. package/out/primary-preference.js +15 -99
  341. package/out/primary-preference.js.map +1 -1
  342. package/out/proximity-rerank.d.ts +41 -30
  343. package/out/proximity-rerank.d.ts.map +1 -1
  344. package/out/proximity-rerank.js +38 -29
  345. package/out/proximity-rerank.js.map +1 -1
  346. package/out/ranking-weights.d.ts +98 -64
  347. package/out/ranking-weights.d.ts.map +1 -1
  348. package/out/ranking-weights.js +26 -25
  349. package/out/ranking-weights.js.map +1 -1
  350. package/out/region-keys.d.ts +50 -31
  351. package/out/region-keys.d.ts.map +1 -1
  352. package/out/region-keys.js +68 -40
  353. package/out/region-keys.js.map +1 -1
  354. package/out/reverse.d.ts +78 -50
  355. package/out/reverse.d.ts.map +1 -1
  356. package/out/reverse.js +98 -61
  357. package/out/reverse.js.map +1 -1
  358. package/out/schema.d.ts +72 -49
  359. package/out/schema.d.ts.map +1 -1
  360. package/out/schema.js +4 -5
  361. package/out/schema.js.map +1 -1
  362. package/out/search-fetch.d.ts +16 -12
  363. package/out/search-fetch.d.ts.map +1 -1
  364. package/out/search-fetch.js +83 -63
  365. package/out/search-fetch.js.map +1 -1
  366. package/out/sqlite/convention-source.d.ts +12 -16
  367. package/out/sqlite/convention-source.d.ts.map +1 -1
  368. package/out/sqlite/convention-source.js +13 -22
  369. package/out/sqlite/convention-source.js.map +1 -1
  370. package/out/sqlite/utils.d.ts +19 -19
  371. package/out/sqlite/utils.d.ts.map +1 -1
  372. package/out/sqlite/utils.js +14 -15
  373. package/out/sqlite/utils.js.map +1 -1
  374. package/out/street/centroid/schema.d.ts +37 -42
  375. package/out/street/centroid/schema.d.ts.map +1 -1
  376. package/out/street/centroid/schema.js +9 -21
  377. package/out/street/centroid/schema.js.map +1 -1
  378. package/out/street/centroid.d.ts +30 -0
  379. package/out/street/centroid.d.ts.map +1 -0
  380. package/out/street/{centroid/index.js → centroid.js} +13 -24
  381. package/out/street/centroid.js.map +1 -0
  382. package/out/street/morphology/fst/builder.d.ts +20 -10
  383. package/out/street/morphology/fst/builder.d.ts.map +1 -1
  384. package/out/street/morphology/fst/builder.js +44 -34
  385. package/out/street/morphology/fst/builder.js.map +1 -1
  386. package/out/street/morphology/fst/loader.d.ts +33 -26
  387. package/out/street/morphology/fst/loader.d.ts.map +1 -1
  388. package/out/street/morphology/fst/loader.js +23 -22
  389. package/out/street/morphology/fst/loader.js.map +1 -1
  390. package/out/street/name-lookup.d.ts +24 -18
  391. package/out/street/name-lookup.d.ts.map +1 -1
  392. package/out/street/name-lookup.js +34 -35
  393. package/out/street/name-lookup.js.map +1 -1
  394. package/out/street/normalize.d.ts +35 -136
  395. package/out/street/normalize.d.ts.map +1 -1
  396. package/out/street/normalize.js +77 -194
  397. package/out/street/normalize.js.map +1 -1
  398. package/out/street/segment-schema.d.ts +79 -39
  399. package/out/street/segment-schema.d.ts.map +1 -1
  400. package/out/street/segment-schema.js +71 -21
  401. package/out/street/segment-schema.js.map +1 -1
  402. package/out/{street/index.js → street.d.ts} +2 -2
  403. package/out/street.d.ts.map +1 -0
  404. package/out/{street/index.d.ts → street.js} +2 -2
  405. package/out/street.js.map +1 -0
  406. package/out/types.d.ts +84 -149
  407. package/out/types.d.ts.map +1 -1
  408. package/out/types.js +0 -6
  409. package/out/types.js.map +1 -1
  410. package/out/unified-schema.d.ts +8 -12
  411. package/out/unified-schema.d.ts.map +1 -1
  412. package/out/unified-schema.js +29 -29
  413. package/out/unified-schema.js.map +1 -1
  414. package/out/uprn/existence.d.ts +28 -17
  415. package/out/uprn/existence.d.ts.map +1 -1
  416. package/out/uprn/existence.js +26 -16
  417. package/out/uprn/existence.js.map +1 -1
  418. package/out/uprn/lookup.d.ts +14 -44
  419. package/out/uprn/lookup.d.ts.map +1 -1
  420. package/out/uprn/lookup.js +21 -57
  421. package/out/uprn/lookup.js.map +1 -1
  422. package/out/uprn/schema.d.ts +18 -40
  423. package/out/uprn/schema.d.ts.map +1 -1
  424. package/out/uprn/schema.js +10 -32
  425. package/out/uprn/schema.js.map +1 -1
  426. package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
  427. package/out/uprn.d.ts.map +1 -0
  428. package/out/{uprn/index.js → uprn.js} +1 -1
  429. package/out/uprn.js.map +1 -0
  430. package/out/weights-overlay-linker.d.ts +138 -144
  431. package/out/weights-overlay-linker.d.ts.map +1 -1
  432. package/out/weights-overlay-linker.js +105 -159
  433. package/out/weights-overlay-linker.js.map +1 -1
  434. package/package.json +91 -596
  435. package/lib/address/point/index.ts +0 -328
  436. package/lib/convention/index.ts +0 -154
  437. package/lib/fts/index.ts +0 -361
  438. package/out/address/index.d.ts.map +0 -1
  439. package/out/address/index.js.map +0 -1
  440. package/out/address/point/index.d.ts +0 -53
  441. package/out/address/point/index.d.ts.map +0 -1
  442. package/out/address/point/index.js +0 -222
  443. package/out/address/point/index.js.map +0 -1
  444. package/out/ancestry/index.d.ts +0 -45
  445. package/out/ancestry/index.d.ts.map +0 -1
  446. package/out/ancestry/index.js +0 -55
  447. package/out/ancestry/index.js.map +0 -1
  448. package/out/convention/index.d.ts +0 -106
  449. package/out/convention/index.d.ts.map +0 -1
  450. package/out/convention/index.js +0 -93
  451. package/out/convention/index.js.map +0 -1
  452. package/out/fst/index.d.ts.map +0 -1
  453. package/out/fst/index.js.map +0 -1
  454. package/out/fts/index.d.ts +0 -184
  455. package/out/fts/index.d.ts.map +0 -1
  456. package/out/fts/index.js +0 -277
  457. package/out/fts/index.js.map +0 -1
  458. package/out/geonames/index.d.ts.map +0 -1
  459. package/out/geonames/index.js.map +0 -1
  460. package/out/nsul/index.d.ts.map +0 -1
  461. package/out/nsul/index.js.map +0 -1
  462. package/out/poi/index.d.ts.map +0 -1
  463. package/out/poi/index.js.map +0 -1
  464. package/out/postal/index.d.ts.map +0 -1
  465. package/out/postal/index.js.map +0 -1
  466. package/out/street/centroid/index.d.ts +0 -40
  467. package/out/street/centroid/index.d.ts.map +0 -1
  468. package/out/street/centroid/index.js.map +0 -1
  469. package/out/street/index.d.ts.map +0 -1
  470. package/out/street/index.js.map +0 -1
  471. package/out/uprn/index.d.ts.map +0 -1
  472. package/out/uprn/index.js.map +0 -1
  473. /package/lib/{geonames/index.ts → geonames.ts} +0 -0
  474. /package/lib/{nsul/index.ts → nsul.ts} +0 -0
  475. /package/lib/{poi/index.ts → poi.ts} +0 -0
  476. /package/lib/{postal/index.ts → postal.ts} +0 -0
  477. /package/lib/{uprn/index.ts → uprn.ts} +0 -0
package/lib/lookup.ts CHANGED
@@ -3,21 +3,18 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `WOFSQLitePlaceLookup` — the resolver implementation backed by `node:sqlite` + a Kysely-typed
7
- * query layer where the queries are non-trivial, and raw SQL where they aren't (FTS5 MATCH, the
8
- * FTS index build).
9
- *
10
- * See `docs/plan/phases/PHASE_4_2_wof_sqlite.md` for the design rationale.
6
+ * `WOFSQLitePlaceLookup` — the resolver implementation backed by `node:sqlite` plus a Kysely-typed query layer where the queries are non-trivial and raw SQL where they are not.
11
7
  */
12
8
 
13
9
  import { expandPlacetypeFilter } from "@mailwoman/codex/placetype-map"
14
10
  import type { Ancestor, CoincidentLocality } from "@mailwoman/core/resolver"
15
11
  import { allRows } from "@mailwoman/core/utils"
16
12
  import { haversineKm } from "@mailwoman/spatial"
17
- import type { SQLInputValue } from "@mailwoman/sqlite/client"
18
- import { DatabaseClient } from "@mailwoman/sqlite/client"
13
+ import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
14
+ import { SQLiteLookup } from "@mailwoman/sqlite/lookup"
15
+ import type { PathBuilderLike } from "path-ts"
19
16
 
20
- import { ancestorLineage } from "#ancestry/index"
17
+ import { ancestorLineage } from "#ancestry"
21
18
  import { candidateFromSearchRow, rankCandidates } from "#candidate/scoring"
22
19
  import { loadCoincidentLocalities } from "#coincident-roles"
23
20
  import {
@@ -28,7 +25,7 @@ import {
28
25
  type ConventionSource,
29
26
  type ResolvedConvention,
30
27
  type Strategy,
31
- } from "#convention/index"
28
+ } from "#convention"
32
29
  import {
33
30
  pickExtractForPlacetype,
34
31
  pickExtractsForPlacetype,
@@ -44,7 +41,7 @@ import {
44
41
  placeBboxExists,
45
42
  placePopulationExists,
46
43
  placeSearchFTSExists,
47
- } from "#fts/index"
44
+ } from "#fts"
48
45
  import { normalizePlacetypes, sanitizeFTSQuery } from "#fts/query"
49
46
  import { cfNormalize, softNameScore } from "#name-score"
50
47
  import { encyclopedicClauses } from "#place-importance-schema"
@@ -57,61 +54,39 @@ import type { FindPlaceQuery, PlaceCandidate, PlaceLookup, WOFPlacetype } from "
57
54
 
58
55
  export interface WOFSQLitePlaceLookupOpts {
59
56
  /**
60
- * Path to the WOF SQLite distribution on disk. Mutually exclusive with `database`.
61
- *
62
- * **Single string** — opens that one DB as the main extract.
63
- *
64
- * **Array** — opens the first entry as main, then ATTACHes each subsequent entry as a separate SQLite schema. Schema
65
- * names are derived from the filename (`whosonfirst-data-postalcode- us-latest.db` → `postalcode_us`); override with
66
- * `ExtractConfig.schemaName` when the filename doesn't follow WOF convention. See `extracts.ts` for the derivation
67
- * rules.
68
- *
69
- * Routing: queries with a `placetype` matching a extract's name (or explicit `placetypes` hint) are sent to that
70
- * extract; everything else hits main. Cross-extract UNION is NOT done — BM25 isn't comparable across
71
- * separately-indexed corpora.
57
+ * Path to the WOF SQLite distribution on disk, mutually exclusive with `database`;
58
+ * an array opens the first entry as main and ATTACHes the rest.
72
59
  */
73
- databasePath?: string | ReadonlyArray<string | ExtractConfig>
60
+ databasePath?: PathBuilderLike | ReadonlyArray<PathBuilderLike | ExtractConfig>
74
61
  /**
75
- * Pre-opened connection — primarily for tests against an inline fixture DB. Mutually exclusive with `databasePath`.
76
- * Multi-extract requires `databasePath` (so the lookup owns the ATTACH).
62
+ * Pre-opened connection, mutually exclusive with `databasePath`; multi-extract requires `databasePath`.
77
63
  */
78
64
  database?: DatabaseClient<WOFDatabase>
79
65
  /**
80
- * If true, build the FTS5 `place_search` virtual table on construction if it doesn't already exist. The upstream WOF
81
- * distribution does NOT ship FTS5, so callers either set this once on first open or pre-build it via the
82
- * operator-side CLI documented in the README. Default false — the resolver assumes the index already exists and
83
- * errors loudly if it doesn't.
66
+ * When true, build the FTS5 `place_search` virtual table on construction if
67
+ * it is missing, on the main extract only.
84
68
  *
85
- * With multi-extract, `buildFTS: true` builds the index on the **main** extract only. Other extracts must be
86
- * pre-built via `mailwoman gazetteer build fts` — operator script for predictable cost.
69
+ * @defaultValue `false`
87
70
  */
88
71
  buildFTS?: boolean
89
72
  /**
90
- * Geographic Rule Engine convention source (Direction E, #289). Per-WOF-polygon resolution profiles, either as a
91
- * ready `ConventionSource` or a plain `{ wofID: Convention }` seed map. Default empty — every query rides
92
- * `WORLD_DEFAULT` (the EU coordinate-first behavior). JP/KR/TW add rows; #290 wires a build-from-source sqlite-backed
93
- * source here.
73
+ * Geographic Rule Engine convention source, either a ready `ConventionSource`
74
+ * or a `{ wofID: Convention }` seed map.
75
+ *
76
+ * @defaultValue `[]`. An empty list resolves every query to `WORLD_DEFAULT`.
94
77
  */
95
78
  conventions?: ConventionSource | Record<number, Convention>
96
79
  /**
97
- * Opt-in postal-city alias reader (#475). When supplied, the coordinate-first locality scorer treats an observed
98
- * `postal_city` ("Antioch", postcode 37013) as a name-match alias for the geographic locality the postcode sits in
99
- * ("Nashville"), recovering the chronic postal-vs- geographic-city mismatch. Absent (the default), the resolver is
100
- * byte-identical — every alias code path is conditioned on this being non-null, so an unprovided reader changes no
101
- * score.
80
+ * Opt-in postal-city alias reader.
81
+ *
82
+ * Absent, every alias code path is skipped and the resolver is byte-identical.
102
83
  */
103
84
  postalCityAliases?: WOFPostalCityAliasLookup
104
85
  }
105
86
 
106
87
  /**
107
- * The coordinate-first candidate table (scripts/build-postcode-locality.ts): postcode → containing
108
- *
109
- * - Nearby localities with WOF alt-name aliases.
110
- */
111
- /**
112
- * The placetypes `pickExtractsForPlacetype`'s substring rule can route by name. Not every WOF placetype — only the ones
113
- * a purpose-built extract is ever named for — so the diagnostic below can say "this name routes nowhere" without
114
- * claiming to enumerate the gazetteer.
88
+ * The placetypes `pickExtractsForPlacetype`'s substring rule can route by name,
89
+ * a subset of the WOF placetypes.
115
90
  */
116
91
  const KNOWN_ROUTED_PLACETYPES: ReadonlyArray<string> = [
117
92
  "postalcode",
@@ -125,86 +100,71 @@ const KNOWN_ROUTED_PLACETYPES: ReadonlyArray<string> = [
125
100
  const POSTCODE_LOCALITY_TABLE = "postcode_locality"
126
101
 
127
102
  /**
128
- * Tunables for the coordinate-first locality soft-score `Score = pc·S_pc + name·S_name + pop·S_pop` (each S in [0,1]).
129
- * The pc/name/pop WEIGHTS now come from the resolved convention's `scoringWeights` (`WORLD_DEFAULT` = 0.6/0.3/0.1 — the
130
- * EU values), so a locale can retune them as data. PC_DECAY_KM sets how fast S_pc falls with distance.
103
+ * Tunables for the coordinate-first locality soft-score
104
+ * `Score = pc·S_pc + name·S_name + pop·S_pop` (each S in [0,1]).
131
105
  */
132
106
  const CF_PC_DECAY_KM = 8
133
107
  /**
134
- * The chosen locality must be within this distance of the postcode's containing locality, else the postcode and the
135
- * parsed city name are judged to disagree (a transposed / wrong-for-the-city postcode) and the `mismatch` flag fires.
136
- * Generous enough that a city-state Ortsteil (~15km from the city centroid) and an abutting town (~few km) are NOT
137
- * flagged, tight enough to catch a wrong city (hundreds of km).
108
+ * The chosen locality must be within this distance of the postcode's containing
109
+ * locality or the `mismatch` flag fires.
138
110
  */
139
111
  const CF_MISMATCH_KM = 50
140
112
 
141
- export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
142
- readonly #db: DatabaseClient<WOFDatabase>
143
- /**
144
- * Resources this instance opened. A connection handed in by a caller is NOT in here, so disposal cannot reach it —
145
- * ownership is membership rather than a flag a later branch has to check.
146
- */
147
- readonly #resources = new DisposableStack()
113
+ export class WOFSQLitePlaceLookup extends SQLiteLookup<WOFDatabase> implements PlaceLookup {
148
114
  readonly #weights: RankingWeights
149
115
  /**
150
- * Cached at construction so we don't `sqlite_master` query on every findPlace call. Bbox + near- with-radius queries
151
- * fall back to no-filter when this is false, preserving compatibility with DBs that were FTS-built before the R*Tree
152
- * shipped.
116
+ * Cached at construction.
153
117
  *
154
- * Per-extract: a extract is only considered to have the bbox index if its own R*Tree table exists.
118
+ * An extract is considered to have the bbox index only if its own R*Tree table exists.
155
119
  */
156
120
  readonly #hasBboxIndex: Map<string, boolean>
157
121
  /**
158
- * Per-extract probe for the `place_population` aux table. When false, the LEFT JOIN is omitted from the SELECT and
159
- * population boost is 0 for every row — preserves compatibility with DBs built before this feature shipped.
122
+ * Per-extract probe for the `place_population` aux table.
123
+ *
124
+ * When false, the left join is omitted and the population boost is 0 for every row.
160
125
  */
161
126
  readonly #hasPopulationIndex: Map<string, boolean>
162
127
  /**
163
- * Per-extract SELECT term + LEFT JOIN for the two-score split's `encyclopedic` carry (ROAD_TO_V9 §2 R1), probed and
164
- * built once at construction. Degrades to `NULL AS encyclopedic` with no join on a pre-split extract — every shipped
165
- * extract today. See {@link encyclopedicClauses} for why the probe is a column and not a table.
128
+ * Per-extract select term and left join for the two-score split's `encyclopedic` value,
129
+ * probed and built once at construction.
166
130
  */
167
131
  readonly #encyclopedicClauses: Map<string, { select: string; join: string }>
168
132
  /**
169
- * Per-extract probe for the `postcode_locality` table (the coordinate-first candidate table, built by
170
- * scripts/build-postcode-locality.ts). Cached at construction; null'd out when absent so the coord-first path
171
- * silently no-ops on a deployment that didn't ship the table.
133
+ * Per-extract probe for the `postcode_locality` table, cached at construction
134
+ * and null when absent so the coord-first path no-ops.
172
135
  */
173
136
  readonly #postcodeLocalityExtract: string | null
174
137
  /**
175
- * Resolved extract list. Always at least one entry; first is `main`. Multi-extract adds extras with their own derived
176
- * (or override) schema names.
138
+ * Resolved extract list, always at least one entry with `main` first.
177
139
  */
178
140
  readonly #extracts: ResolvedExtract[]
179
141
  /**
180
- * #920: per-schema probed country sets for country-aware extract routing (non-main extracts only).
142
+ * Per-schema probed country sets for country-aware extract routing, non-main extracts only.
181
143
  */
182
144
  readonly #extractCountries: Map<string, ReadonlySet<string>>
183
145
  /**
184
- * The Geographic Rule Engine (Direction E, #289). `#conventionSource` supplies per-WOF-polygon resolution profiles;
185
- * `#strategies` is the named-primitive registry the merged convention dispatches. Empty source → every query resolves
186
- * to `WORLD_DEFAULT` → byte-identical to the pre-engine coordinate-first path. `#countryWOFIdCache` memoizes the
187
- * country-code → country-WOF-id lookup that seeds the convention ancestor chain (one query per country, then
188
- * cached).
146
+ * `#conventionSource` supplies per-WOF-polygon profiles for the Geographic Rule Engine.
147
+ *
148
+ * `#strategies` maps primitive names to strategies.
149
+ * `#countryWOFIdCache` memoizes country-code to country-WOF-id lookups.
189
150
  */
190
151
  readonly #conventionSource: ConventionSource
191
152
  readonly #strategies: Map<string, Strategy>
192
153
  readonly #countryWOFIdCache = new Map<string, number | null>()
193
154
  /**
194
- * Strategy names already warned about — so an unknown name surfaces once, not once per query.
155
+ * Strategy names already warned about, so an unknown name surfaces once.
195
156
  */
196
157
  readonly #warnedUnknownStrategies = new Set<string>()
197
158
  /**
198
- * Lazily-built `admin_id → coincident localities` map from the #403 relation (null until first use).
159
+ * Lazily-built `admin_id → coincident localities` map, null until first use.
199
160
  */
200
161
  #coincidentRolesCache: Map<number, CoincidentLocality[]> | null = null
201
162
  /**
202
- * Per-id memoized ancestor lineages (#404) — a hot chain is queried once.
163
+ * Per-id memoized ancestor lineages, queried once per chain.
203
164
  */
204
165
  readonly #ancestorsCache = new Map<number, Ancestor[]>()
205
166
  /**
206
- * Opt-in postal-city alias reader (#475). `null` unless `opts.postalCityAliases` was supplied — every alias code path
207
- * is conditioned on this, so the default resolver is byte-identical.
167
+ * Opt-in postal-city alias reader, `null` unless supplied, so every alias path is skipped by default.
208
168
  */
209
169
  readonly #postalCityAliases: WOFPostalCityAliasLookup | null
210
170
 
@@ -217,29 +177,26 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
217
177
  throw new Error("WOFSQLitePlaceLookup: one of `database` or `databasePath` is required")
218
178
  }
219
179
 
220
- if (opts.database) {
221
- this.#db = opts.database
222
- this.#extracts = [{ path: ":memory:", schemaName: "main", placetypes: [] }]
223
- } else {
224
- const extracts = resolveExtracts(opts.databasePath!)
225
- this.#extracts = extracts
226
- // Read-only by default — shipped gazetteer extracts are sealed 0444 and Docker `:ro` mounts
227
- // forbid write-mode opens, so a writable open fails there. The only code path that writes to
228
- // the main extract is `#ensureFTS()` (FTS5 index build), conditioned on `opts.buildFTS`; open writable
229
- // ONLY when that build was explicitly requested. Every read query (FTS5 MATCH, the aux-table
230
- // SELECTs, ATTACH, and the `busy_timeout` PRAGMA) works read-only. See the docker read-only
231
- // mount limitation (#1213).
232
- this.#db = this.#resources.use(new DatabaseClient<WOFDatabase>(extracts[0]!.path, { readOnly: !opts.buildFTS }))
233
-
234
- // ATTACH each non-main extract. Schema names were validated by resolveExtracts, so safe to
235
- // interpolate directly (SQLite ATTACH doesn't accept parameters for the schema name).
236
- for (const s of extracts.slice(1)) {
237
- this.#db.exec(`ATTACH DATABASE '${s.path.replaceAll("'", "''")}' AS ${s.schemaName}`)
238
- }
180
+ const extracts: ResolvedExtract[] = opts.database
181
+ ? [{ path: ":memory:", schemaName: "main", placetypes: [] }]
182
+ : resolveExtracts(opts.databasePath!)
183
+
184
+ // Read-only by default: shipped extracts are sealed 0444 and Docker `:ro`
185
+ // mounts forbid a write-mode open.
186
+ // Only `buildFTS` opens writable.
187
+ super(opts.database ? { database: opts.database } : { databasePath: extracts[0]!.path }, {
188
+ readOnly: !opts.buildFTS,
189
+ })
190
+
191
+ this.#extracts = extracts
192
+
193
+ // Schema names were validated by resolveExtracts, so interpolating them is safe.
194
+ // SQLite attach accepts no parameter for a schema name.
195
+ for (const s of extracts.slice(1)) {
196
+ this.database.exec(`ATTACH DATABASE '${s.path.replaceAll("'", "''")}' AS ${s.schemaName}`)
239
197
  }
240
198
 
241
- // node:sqlite has no .pragma() helper; pragmas are executed as plain SQL.
242
- this.#db.exec("PRAGMA busy_timeout = 5000")
199
+ this.database.exec("PRAGMA busy_timeout = 5000")
243
200
 
244
201
  if (opts.buildFTS) {
245
202
  this.#ensureFTS()
@@ -249,8 +206,6 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
249
206
 
250
207
  this.#weights = { ...DEFAULT_WEIGHTS, ...weights }
251
208
 
252
- // Probe each extract's aux-table presence — driven by per-extract table existence in
253
- // sqlite_master. Cached at construction so findPlace doesn't query sqlite_master per call.
254
209
  this.#hasBboxIndex = new Map()
255
210
  this.#hasPopulationIndex = new Map()
256
211
  this.#encyclopedicClauses = new Map()
@@ -258,25 +213,12 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
258
213
  for (const s of this.#extracts) {
259
214
  this.#hasBboxIndex.set(s.schemaName, this.#extractHasTable(s.schemaName, PLACE_BBOX_TABLE))
260
215
  this.#hasPopulationIndex.set(s.schemaName, this.#extractHasTable(s.schemaName, PLACE_POPULATION_TABLE))
261
- this.#encyclopedicClauses.set(s.schemaName, encyclopedicClauses(this.#db, s.schemaName))
216
+ this.#encyclopedicClauses.set(s.schemaName, encyclopedicClauses(this.database, s.schemaName))
262
217
  }
263
218
 
264
- // Every lookup path here reaches `place_search`, and a extract without it fails in one of two ways
265
- // that are both hard to read: an unroutable name returns zero hits (indistinguishable from "this
266
- // country has no places") and a routable one throws mid-query from deep inside a SELECT. The
267
- // unroutable half is the worse of the two — a extract reaches routing only through the name
268
- // `deriveSchemaName` derives from its FILENAME, so a file spelled one letter off the placetype it
269
- // serves answers with nothing while holding every row that was asked for.
270
- //
271
- // Two independent things bring a extract under the guard, and it needs both. Carrying `spr` is a
272
- // CLAIM to be a place extract. Carrying a name that routes is an INVITATION to be queried as one, and
273
- // it is made by the filename alone — so a database with no tables at all still gets picked, still
274
- // answers no query, and still dies inside a SELECT. Testing only the claim lets an empty or
275
- // truncated file past construction; testing only the name would exempt a correctly-named build
276
- // input. A extract needs to fail neither test to be exempt.
277
- //
278
- // Exempt by design: `postcode-locality-<cc>.db` carries a relation table and nothing else, matches
279
- // no routed placetype, and is part of the documented default extract list.
219
+ // An extract is guarded when its path identifies a routed placetype
220
+ // or it contains `spr`, and must then contain `place_search`; testing only one of
221
+ // those would let an empty file through or exempt a build input.
280
222
  for (const s of this.#extracts) {
281
223
  if (s.schemaName === "main") continue
282
224
 
@@ -304,42 +246,31 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
304
246
  )
305
247
  }
306
248
 
307
- // #920 country-aware extract routing: probe each NON-MAIN extract's country set once at
308
- // construction (they're small, purpose-built extracts — postcode/locality slices; main is the
309
- // multi-GB admin DB and is the fallback anyway, so it is deliberately NOT scanned). Feeds
310
- // pickExtractForPlacetype so two postcode extracts (postalcode-us + postalcode-geonames-tail)
311
- // route by the query's country instead of first-match starving the second extract.
249
+ // Probe each non-main extract's country set once at construction so two postcode
250
+ // extracts route by the query's country instead of first-match starving the second.
312
251
  this.#extractCountries = new Map()
313
252
 
314
253
  for (const sh of this.#extracts) {
315
254
  if (sh.schemaName === "main") continue
316
255
 
317
256
  try {
318
- const rows = this.#db
257
+ const rows = this.database
319
258
  .prepare(`SELECT DISTINCT country FROM ${sh.schemaName}.spr WHERE country != ''`)
320
259
  .all() as Array<{ country: string }>
321
260
 
322
261
  this.#extractCountries.set(sh.schemaName, new Set(rows.map((r) => r.country)))
323
262
  } catch {
324
- // A extract without spr (or an attach oddity) just doesn't participate in country routing.
263
+ // An extract without spr (or an attach oddity) just doesn't participate in country routing.
325
264
  }
326
265
  }
327
266
 
328
- // The postcode_locality table can live on any attached extract (typically its own
329
- // `postcode-locality-<cc>.db`). Find the first extract that has it; null = coord-first disabled.
330
267
  this.#postcodeLocalityExtract =
331
268
  this.#extracts.find((s) => this.#extractHasTable(s.schemaName, POSTCODE_LOCALITY_TABLE))?.schemaName ?? null
332
269
 
333
- // Opt-in postal-city alias reader (#475). Construction-time present-or-not is the check: null
334
- // keeps the coordinate-first scorer byte-identical to pre-#475.
335
270
  this.#postalCityAliases = opts.postalCityAliases ?? null
336
271
 
337
- // The Geographic Rule Engine convention source. Precedence: an explicit `opts.conventions`
338
- // (a ready source or a seed map) wins; else the build-from-source convention asset if one is
339
- // attached (auto-detected, like the postcode_locality extract — adding conventions.db to
340
- // databasePath enables it; queried on demand, not paged into memory); else empty, so EU rides
341
- // WORLD_DEFAULT. The registry binds strategy NAMES to the SQL-bound primitives — adding a
342
- // strategy is registering it here.
272
+ // Precedence: explicit `opts.conventions` wins, else an attached build-from-source
273
+ // convention asset, else empty so EU uses `WORLD_DEFAULT`.
343
274
  const conventionExtract =
344
275
  this.#extracts.find((s) => this.#extractHasTable(s.schemaName, ADDRESS_CONVENTION_TABLE))?.schemaName ?? null
345
276
 
@@ -348,7 +279,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
348
279
  ? opts.conventions
349
280
  : new SeedConventionSource(opts.conventions as Record<number, Convention>)
350
281
  : conventionExtract
351
- ? new SqliteConventionSource(this.#db, conventionExtract)
282
+ ? new SqliteConventionSource(this.database, conventionExtract)
352
283
  : new SeedConventionSource()
353
284
 
354
285
  this.#strategies = new Map<string, Strategy>([
@@ -358,15 +289,15 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
358
289
  }
359
290
 
360
291
  #extractHasTable(schemaName: string, tableName: string): boolean {
361
- // For main, the existing helpers work directly. For attached extracts we have to ask via the
362
- // schema-qualified `sqlite_master` view.
292
+ // Main uses the existing helpers.
293
+ // Attached extracts need the schema-qualified `sqlite_master`.
363
294
  if (schemaName === "main") {
364
- if (tableName === PLACE_BBOX_TABLE) return placeBboxExists(this.#db)
295
+ if (tableName === PLACE_BBOX_TABLE) return placeBboxExists(this.database)
365
296
 
366
- if (tableName === PLACE_POPULATION_TABLE) return placePopulationExists(this.#db)
297
+ if (tableName === PLACE_POPULATION_TABLE) return placePopulationExists(this.database)
367
298
  }
368
299
 
369
- const row = this.#db
300
+ const row = this.database
370
301
  .prepare(`SELECT name FROM ${schemaName}.sqlite_master WHERE type = 'table' AND name = ?`)
371
302
  .get(tableName) as { name: string } | undefined
372
303
 
@@ -374,15 +305,11 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
374
305
  }
375
306
 
376
307
  async findPlace(query: FindPlaceQuery): Promise<PlaceCandidate[]> {
377
- // Geographic Rule Engine dispatch (#289). Resolve the effective convention for this query
378
- // (WORLD_DEFAULT for the EU locales — the seed source is empty) and run its candidate strategies
379
- // in order; the first to return a non-null result wins. The default list,
380
- // [postcode_area_resolution, fallback_fuzzy_name_match], reproduces the pre-engine coordinate-
381
- // first → FTS fall-through exactly. Unknown strategy names are skipped, so a convention may name
382
- // a primitive a future phase will register.
308
+ // Run the effective convention's candidate strategies in order.
309
+ // The first non-null result wins and unknown strategy names are skipped.
383
310
  const convention = this.#conventionFor(query)
384
311
 
385
- let outcome: PlaceCandidate[] = []
312
+ let found: PlaceCandidate[] = []
386
313
 
387
314
  for (const name of convention.candidateStrategies) {
388
315
  const strategy = this.#strategies.get(name)
@@ -395,23 +322,17 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
395
322
 
396
323
  const result = await strategy(query, convention)
397
324
 
398
- if (result !== null) {
399
- outcome = result
325
+ if (result) {
326
+ found = result
400
327
 
401
328
  break
402
329
  }
403
330
  }
404
331
 
405
- if (outcome.length) return outcome
332
+ if (found.length) return found
406
333
 
407
- // #924: NL postcode retry ladder. The WOF NL postalcode repo stores full codes UNSPACED
408
- // ('1012LG') plus 4-digit stems ('1012'), while Dutch addresses carry the spaced form
409
- // ('1012 LG') — two FTS tokens that can never match the one-token doc (the #920 name law,
410
- // resurfacing in a WOF-built extract). On a postcode-typed NL-shape miss, retry ONCE with the
411
- // whitespace-joined form (block-level precision when the full-code row exists), then the
412
- // 4-digit stem (area-level). Country-restricted to NL — the same digits+letters shape elsewhere
413
- // must not silently coarsen to a different system's code. Each retry only fires when its
414
- // text differs from the current one, so the ladder terminates by construction.
334
+ // On a postcode-typed NL-shape miss, retry with the whitespace-joined form then the 4-digit stem,
335
+ // restricted to NL so the same shape elsewhere cannot coarsen to another system's code.
415
336
  if (
416
337
  query.country?.toUpperCase() === "NL" &&
417
338
  (normalizePlacetypes(query.placetype)?.includes("postalcode") ?? false) &&
@@ -431,14 +352,11 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
431
352
  if (stem !== trimmed) return this.findPlace({ ...query, text: stem })
432
353
  }
433
354
 
434
- return outcome
355
+ return found
435
356
  }
436
357
 
437
358
  /**
438
- * Dual-role localities coincident with an admin id, from the precomputed `coincident_roles` relation (#403). Backs
439
- * {@link ResolveOpts.hierarchyCompletion} (#405): O(1) once the relation is loaded. Returns `[]` when the relation
440
- * table is absent (older DB) or the admin isn't a dual-role place, so completion degrades gracefully. The relation +
441
- * `spr` join is loaded once and memoized.
359
+ * Dual-role localities coincident with an admin id, or `[]` when the relation table is absent.
442
360
  */
443
361
  coincidentLocalitiesFor(adminID: number | string): CoincidentLocality[] {
444
362
  const id = typeof adminID === "number" ? adminID : Number(adminID)
@@ -446,20 +364,15 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
446
364
  if (!Number.isFinite(id)) return []
447
365
 
448
366
  if (!this.#coincidentRolesCache) {
449
- this.#coincidentRolesCache = loadCoincidentLocalities(this.#db)
367
+ this.#coincidentRolesCache = loadCoincidentLocalities(this.database)
450
368
  }
451
369
 
452
370
  return this.#coincidentRolesCache.get(id) ?? []
453
371
  }
454
372
 
455
373
  /**
456
- * The ancestor lineage of a place — its containment chain joined with `spr` for canonical names, ordered
457
- * NEAREST-FIRST (localadmin → county → region → … → country). Backs {@link ResolveOpts.includeAncestors} (#404). Self
458
- * is excluded; memoized per id. Returns `[]` when the place has no recorded ancestry.
459
- *
460
- * The walk itself lives in `ancestry.ts` (shared with the reverse geocoder, #484); the ordering is its
461
- * `PLACETYPE_DEPTH` table — same ranking as the previous inline SQL CASE, extended below `localadmin` so
462
- * locality/neighbourhood ancestors order correctly instead of sorting last.
374
+ * The ancestor lineage of a place, ordered nearest-first with self excluded,
375
+ * or `[]` when it has no recorded ancestry.
463
376
  */
464
377
  ancestors(id: number | string): Ancestor[] {
465
378
  const pid = typeof id === "number" ? id : Number(id)
@@ -469,7 +382,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
469
382
 
470
383
  if (cached) return cached
471
384
 
472
- const lineage: Ancestor[] = ancestorLineage(this.#db, pid).map((r) => ({
385
+ const lineage: Ancestor[] = ancestorLineage(this.database, pid).map((r) => ({
473
386
  id: r.id,
474
387
  placetype: r.placetype,
475
388
  name: r.name,
@@ -481,10 +394,8 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
481
394
  }
482
395
 
483
396
  /**
484
- * Surface an unknown strategy name LOUDLY (once per name) rather than swallowing it silently — an invisible no-op is
485
- * exactly the hidden-dependency failure mode we avoid (see the provenance-first design value). We warn rather than
486
- * throw so a convention asset built against a newer code revision (one that adds a strategy) degrades gracefully on
487
- * an older build instead of taking down resolution.
397
+ * Warn once per unknown strategy name rather than throwing, so a convention built
398
+ * against a newer revision degrades instead of stopping resolution.
488
399
  */
489
400
  #warnUnknownStrategy(name: string): void {
490
401
  if (this.#warnedUnknownStrategies.has(name)) return
@@ -498,10 +409,8 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
498
409
  }
499
410
 
500
411
  /**
501
- * Strategy `postcode_area_resolution` — the coordinate-first locality path, strictly conditioned (a sibling postcode
502
- * AND a postcode_locality table AND a locality query). Returns `null` — so the dispatcher falls through to the next
503
- * strategy — when the condition is unmet or the postcode isn't in the table; otherwise the soft-scored postcode∪name
504
- * candidate set.
412
+ * The coordinate-first locality strategy, returning `null` when its postcode,
413
+ * table, or locality conditions are unmet.
505
414
  */
506
415
  #postcodeAreaResolution(query: FindPlaceQuery, convention: ResolvedConvention): Promise<PlaceCandidate[] | null> {
507
416
  if (!(query.postcode && this.#postcodeLocalityExtract && this.#isLocalityQuery(query))) {
@@ -512,34 +421,30 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
512
421
  }
513
422
 
514
423
  /**
515
- * Strategy `fallback_fuzzy_name_match` — the BM25 FTS name-match over the gazetteer, the universal fallback. Always
516
- * returns an array (never null), so it terminates the dispatch chain.
424
+ * The BM25 FTS name-match fallback, always returning an array so it terminates the dispatch chain.
517
425
  */
518
426
  async #fuzzyNameMatch(query: FindPlaceQuery, forceExtract?: ResolvedExtract): Promise<PlaceCandidate[]> {
519
427
  const limit = query.limit ?? 10
520
428
 
521
- // Expand the placetype filter through the shared equivalence table (core/resolver): a
522
- // `locality` query must also reach `borough` / `localadmin` rows — Brooklyn-the-borough
523
- // (pop 2.5M) is a borough, not a locality, and a strict filter made it unreachable so the
524
- // fuzzy "Brooklyn Park, MN" won instead. Order-preserving: the FIRST entry stays the
525
- // requested placetype, which is what extract routing keys off below.
429
+ // Expand the placetype filter through the shared equivalence table so a `locality`
430
+ // query also reaches `borough` and `localadmin` rows.
526
431
  const placetypes = expandPlacetypeFilter(normalizePlacetypes(query.placetype)) as WOFPlacetype[] | null
527
- // Postcode-typed queries keep the #920 fused name-law shape; everything else splits on
528
- // intra-token punctuation so hyphenated names reach the FTS as their real terms (#945).
432
+ // Postcode-typed queries keep the fused name-law shape.
433
+ // Everything else splits on intra-token punctuation so hyphenated names
434
+ // reach the FTS as their real terms.
529
435
  const ftsQuery = sanitizeFTSQuery(query.text, { fuseTokens: placetypes?.includes("postalcode") ?? false })
530
436
 
531
437
  if (!ftsQuery) return []
532
438
 
533
- // Pick the extract for this query. Multi-extract routing is placetype-driven; a query without
534
- // `placetype` always goes to main. (Mixed-placetype queries with multiple extracts aren't
535
- // supported in v1 — caller can issue two findPlace calls and merge in TS if needed.)
439
+ // Multi-extract routing uses placetype.
440
+ // Queries without `placetype` go to main.
441
+ // Mixed-placetype spread across extracts is unsupported.
536
442
  const firstPlacetype = placetypes?.[0]
537
443
 
538
- // Bias fan-out (#58/proximity-bias): a country-less query WITH proximity hints must see the
539
- // cross-extract ambiguity the hints exist to resolve — "48026" lives in postalcode-us AND
540
- // postalcode-intl, and single-extract routing would hide one side. Query every matching extract
541
- // (self-recursion with a extract pin), merge by id, and re-sort by the same (exact, prominence)
542
- // keys the per-extract tier sort used. Bounded: hints + no country + >1 matching extract only.
444
+ // A country-less query with proximity hints queries every matching extract
445
+ // so cross-extract ambiguity is visible.
446
+ // The bound requires hints and an absent country.
447
+ // It also requires more than one matching extract.
543
448
  const hasBiasHints = !!query.near || (query.bias?.length ?? 0) > 0
544
449
 
545
450
  if (!forceExtract && hasBiasHints && !query.country) {
@@ -575,16 +480,16 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
575
480
 
576
481
  const extract =
577
482
  forceExtract ??
578
- pickExtractForPlacetype(this.#extracts, firstPlacetype, {
483
+ pickExtractForPlacetype(this.#extracts, firstPlacetype ?? null, {
579
484
  country: query.country,
580
485
  countriesBySchema: this.#extractCountries,
581
486
  })
582
487
 
583
- // bare schema name; safe to interpolate (validated at construction)
488
+ // Validated at construction, so bare interpolation is safe.
584
489
  const sch = extract.schemaName
585
490
 
586
491
  const rawRows = fetchSearchRows({
587
- db: this.#db,
492
+ db: this.database,
588
493
  schemaName: sch,
589
494
  query,
590
495
  placetypes,
@@ -606,7 +511,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
606
511
  const candidates = rawRows.map((row) => candidateFromSearchRow(row, scoring))
607
512
 
608
513
  rankCandidates(candidates, {
609
- db: this.#db,
514
+ db: this.database,
610
515
  schemaName: sch,
611
516
  query,
612
517
  weights: this.#weights,
@@ -622,11 +527,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
622
527
  }
623
528
 
624
529
  /**
625
- * Resolve the effective convention for a query (the Geographic Rule Engine entry point). The ancestor chain is keyed
626
- * by WOF polygon id; for #289 it carries just the country level — resolved from `query.country` via the cached
627
- * code→WOF-id lookup — so the EU locales, which have no override rows, resolve to `WORLD_DEFAULT` and dispatch is
628
- * byte-identical to the pre-engine path. E4 (JP) extends the chain with the resolved locality's `ancestors` row, so a
629
- * region/locality-level convention (e.g. Sapporo's grid) deep-merges over the country one.
530
+ * Resolve the effective convention for a query, keyed by the country's WOF polygon id.
630
531
  */
631
532
  #conventionFor(query: FindPlaceQuery): ResolvedConvention {
632
533
  const chain: number[] = []
@@ -643,8 +544,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
643
544
  }
644
545
 
645
546
  /**
646
- * Country ISO code → its WOF polygon id (the coarsest convention key). Cached — one indexed `spr` query per distinct
647
- * country, then memoized (including a not-found `null`) so findPlace never pays for it twice.
547
+ * Country ISO code to its WOF polygon id, memoized including the not-found `null`.
648
548
  */
649
549
  #countryWOFId(code: string): number | null {
650
550
  const cached = this.#countryWOFIdCache.get(code)
@@ -653,7 +553,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
653
553
  let id: number | null
654
554
 
655
555
  try {
656
- const row = this.#db
556
+ const row = this.database
657
557
  .prepare(`SELECT id FROM main.spr WHERE placetype = 'country' AND country = ? AND is_current != 0 LIMIT 1`)
658
558
  .get(code) as { id: number } | undefined
659
559
 
@@ -668,12 +568,9 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
668
568
  }
669
569
 
670
570
  /**
671
- * Coordinate-first locality resolution. The postcode_locality table maps the sibling postcode to the locality whose
672
- * polygon contains the postcode centroid (+ a few nearby ones for the abutting- postcode case). We union those
673
- * COORDINATE candidates with the FTS NAME candidates and soft-score the union `0.6·S_pc + 0.3·S_name + 0.1·S_pop` —
674
- * so a small town the name-match never finds is recovered by the postcode, while an unambiguous name (Berlin) still
675
- * wins on name + population. Returns null when the postcode isn't in the table (→ caller falls back to the FTS
676
- * path).
571
+ * Coordinate-first locality resolution: union the postcode's containing
572
+ * and nearby localities with the FTS name candidates and soft-score them,
573
+ * returning null when the postcode is not in the table.
677
574
  */
678
575
  async #findLocalityCoordFirst(
679
576
  query: FindPlaceQuery,
@@ -686,7 +583,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
686
583
  const pcParams: SQLInputValue[] = query.country ? [pc, query.country] : [pc]
687
584
 
688
585
  const pcRows = allRows<{ id: number; aliases: string | null; dist: number; containing: number }>(
689
- this.#db.prepare(
586
+ this.database.prepare(
690
587
  `SELECT locality_id AS id, aliases, distance_km AS dist, is_containing AS containing
691
588
  FROM ${sch}.${POSTCODE_LOCALITY_TABLE} WHERE ${pcWhere}`
692
589
  ),
@@ -696,7 +593,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
696
593
  if (!pcRows.length) return null
697
594
 
698
595
  const limit = query.limit ?? 10
699
- // Name-match candidates via the normal FTS path (postcode cleared → no recursion).
596
+ // Name-match candidates via the normal FTS path with the postcode cleared, so this cannot recurse.
700
597
  const ftsCands = await this.findPlace({ ...query, postcode: undefined, limit: Math.max(limit, 10) })
701
598
 
702
599
  const pcInfo = new Map<number, { dist: number; containing: boolean; aliases: string[] }>()
@@ -705,10 +602,8 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
705
602
  pcInfo.set(r.id, { dist: r.dist, containing: r.containing === 1, aliases: r.aliases ? r.aliases.split("|") : [] })
706
603
  }
707
604
 
708
- // #475 (opt-in): observed postal-city aliases for this postcode, keyed by the geographic
709
- // locality name they map to. A user-typed postal city ("Antioch", 37013) becomes a name-match
710
- // alias for the geographic locality the postcode sits in ("Nashville"). Empty when the reader
711
- // isn't supplied → the scoring loop below is byte-identical to pre-#475.
605
+ // Observed postal-city aliases for this postcode keyed by geographic locality name,
606
+ // empty when the reader is not supplied.
712
607
  const postalAliasByGeo = new Map<string, string[]>()
713
608
 
714
609
  if (this.#postalCityAliases) {
@@ -743,9 +638,8 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
743
638
  for (const cand of merged.values()) {
744
639
  const info = pcInfo.get(cand.id as number)
745
640
  const sPc = info ? (info.containing ? 1 : Math.exp(-info.dist / CF_PC_DECAY_KM)) : 0
746
- // Fold any postal-city aliases for this candidate's geographic name into the soft name match
747
- // (#475). `postalAliasByGeo` is empty unless the opt-in reader was supplied, so when off this
748
- // reduces to the original `info?.aliases ?? []` and the score is unchanged.
641
+ // Fold postal-city aliases into the soft name match.
642
+ // The map is empty unless the opt-in reader was supplied, so scoring is unchanged when off.
749
643
  const wofAliases = info?.aliases ?? []
750
644
 
751
645
  const aliases = postalAliasByGeo.size
@@ -753,30 +647,23 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
753
647
  : wofAliases
754
648
 
755
649
  const sName = softNameScore(query.text, cand.name, aliases)
756
- const sPop = populationScaleTerm(cand.population, this.#weights)
650
+ const sPop = populationScaleTerm(cand.population ?? null, this.#weights)
757
651
  scored.push({ ...cand, score: w.pc * sPc + w.name * sName + w.pop * sPop, exact: sName >= 1 })
758
652
  }
759
653
 
760
- // Exact-name tiering (same philosophy as the FTS path): an EXACT name/alias match tiers above
761
- // coordinate-only candidates, with the soft-score breaking ties WITHIN a tier. This keeps an
762
- // unambiguous city ("Berlin", exact + huge population) ahead of the fine-grained Ortsteil its
763
- // postcode centroid lands in, while a small town the name-match never finds (no exact tier) is
764
- // still recovered by its postcode's containing locality.
654
+ // An exact name or alias match tiers above coordinate-only candidates,
655
+ // with the soft score breaking ties within a tier.
765
656
  scored.sort((a, b) => Number(b.exact) - Number(a.exact) || b.score - a.score)
766
657
 
767
- // Conflict flag: if the chosen locality is NOT the postcode's containing locality and sits far
768
- // from it, the postcode and the city name disagree (a transposed / wrong-for-the-city postcode).
769
- // We keep the name-chosen locality but flag it — the falsehood signal a BM25 geocoder can't give.
658
+ // When the chosen locality is not the postcode's containing locality and sits far
659
+ // from it, flag `mismatch` rather than overriding the name.
770
660
  const top = scored[0]
771
661
 
772
662
  if (top) {
773
- // The postcode's geographic anchor: among the postcode's candidate localities that actually
774
- // resolved (some — e.g. unnamed Ortsteile — are in the postcode table but not the admin DB),
775
- // prefer the containing one, else the nearest. Postcodes whose centroid falls just outside
776
- // every locality polygon still anchor to the closest town.
663
+ // Among the postcode's candidate localities that resolved, prefer the containing one, else the nearest.
777
664
  const anchorRow = pcRows
778
665
  .filter((r) => merged.has(r.id))
779
- // oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array; toSorted would double-allocate on a hot path
666
+ // oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array. toSorted would double-allocate on a hot path
780
667
  .sort((a, b) => b.containing - a.containing || a.dist - b.dist)[0]
781
668
 
782
669
  const anchor = anchorRow ? merged.get(anchorRow.id) : undefined
@@ -798,7 +685,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
798
685
  }
799
686
 
800
687
  /**
801
- * Fetch locality spr rows (from main) for the postcode-injected candidate ids the FTS set missed.
688
+ * Fetch locality spr rows from main for the postcode-injected candidate ids the FTS set missed.
802
689
  */
803
690
  #fetchLocalitiesByID(ids: number[]): PlaceCandidate[] {
804
691
  if (!ids.length) return []
@@ -808,7 +695,7 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
808
695
  const ph = ids.map(() => "?").join(", ")
809
696
 
810
697
  const rows = allRows<RawSearchRow>(
811
- this.#db.prepare(
698
+ this.database.prepare(
812
699
  `SELECT s.id AS id, s.name AS name, s.country AS country, s.parent_id AS parent_id,
813
700
  s.latitude AS lat, s.longitude AS lon, s.placetype AS placetype, ${popSelect}
814
701
  FROM main.spr s ${popJoin}
@@ -818,45 +705,43 @@ export class WOFSQLitePlaceLookup implements PlaceLookup, Disposable {
818
705
  )
819
706
 
820
707
  return rows.map((row) => {
821
- const c: PlaceCandidate = {
708
+ return {
822
709
  id: row.id,
823
710
  name: row.name,
824
711
  placetype: row.placetype as WOFPlacetype,
825
712
  country: row.country ?? "",
826
713
  lat: row.lat ?? 0,
827
714
  lon: row.lon ?? 0,
828
- parent_id: row.parent_id ?? undefined,
715
+ parent_id: row.parent_id ?? null,
829
716
  score: 0,
830
- }
831
-
832
- if (row.population !== null && row.population > 0) {
833
- c.population = row.population
834
- }
835
-
836
- return c
717
+ population: row.population !== null && row.population > 0 ? row.population : null,
718
+ distanceKm: null,
719
+ exactMatch: null,
720
+ prominence: null,
721
+ referential: null,
722
+ encyclopedic: null,
723
+ importance: null,
724
+ bbox: null,
725
+ mismatch: null,
726
+ containedByQualifier: null,
727
+ regionScopeMiss: null,
728
+ variantAliasExempted: null,
729
+ } satisfies PlaceCandidate
837
730
  })
838
731
  }
839
732
 
840
- [Symbol.dispose](): void {
841
- // Only when we opened it. A caller who passed a pre-opened client keeps using it after this returns — the FTS
842
- // build this lookup performed lives on their connection, and closing it would take that with us.
843
- this.#resources[Symbol.dispose]()
844
- }
845
-
846
733
  /**
847
- * Build the FTS5 virtual table from the `names` + `places` tables.
734
+ * Build the FTS5 virtual table from the `names` and `places` tables.
848
735
  */
849
736
  #ensureFTS(): void {
850
- buildPlaceSearchFTS(this.#db)
737
+ buildPlaceSearchFTS(this.database)
851
738
  }
852
739
 
853
740
  #assertFTSExists(): void {
854
- if (!placeSearchFTSExists(this.#db)) {
741
+ if (!placeSearchFTSExists(this.database)) {
855
742
  throw new Error(
856
743
  "WOFSQLitePlaceLookup: `place_search` FTS5 table is missing. Pass `buildFTS: true` to build it on open, or run `mailwoman gazetteer build fts <path-to-wof.db>` ahead of time (see resolver-wof-sqlite/README.md)."
857
744
  )
858
745
  }
859
746
  }
860
747
  }
861
-
862
- export type { RankingWeights } from "#ranking-weights"