@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/build/slim.ts CHANGED
@@ -2,62 +2,33 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
- *
6
- * Build a "slim" Who's On First SQLite distribution that's small enough to ship as a static asset
7
- * for the browser-side mailwoman demo (Path B of the demo plan). The full admin distribution is
8
- * ~2 GB; the slim variant aims for the ~50–100 MB range by keeping only the places a public demo
9
- * will actually query for.
10
- *
11
- * Selection policy (v1, US-focused):
12
- *
13
- * - All countries / regions / counties / boroughs in the configured `countries` set, so the ancestor
14
- * chain a locality / postcode reports through `parent_id` stays intact.
15
- * - Top-K localities by population (read from the source's pre-built `place_population` aux table) in
16
- * those countries.
17
- * - All postcodes in those countries — they're small and addressing-relevant.
18
- * - All `names` + `place_population` rows for selected place IDs.
19
- * - The `coincident_roles` dual-role relation (#402), filtered to surviving spr ids.
20
- *
21
- * No geojson, by design. The upstream WOF GeoJSON bodies live ONLY in the raw `whosonfirst-data-*`
22
- * repos; `scripts/build-unified-wof.ts` extracts `wof:population` straight into
23
- * `place_population` (and the bbox into `spr`) at ingest and never persists a `geojson` table. So
24
- * the source admin DB carries population in `place_population`, and this builder consumes it
25
- * directly — there is nothing to extract from, and nothing to drop.
26
- *
27
- * The output DB has the resolver-facing schema: `spr`, `names`, `place_population`, plus the
28
- * `place_search` FTS5 / `place_bbox` R*Tree virtual tables rebuilt against the trimmed row set
29
- * (both derive purely from `spr` + `names` — see `fts.ts`). That means `WOFSQLitePlaceLookup`
30
- * opens the slim DB without any code change — it sees a smaller universe, nothing more.
31
- *
32
- * Multi-extract inputs (e.g. admin + postcode) are processed in sequence; selected rows accumulate
33
- * into the single output DB. The postcode extract contributes only postcodes; admin contributes
34
- * everything else. Empty / missing input paths are skipped (callers pass `""` when a extract, such
35
- * as a custom postcode DB, isn't built yet).
36
5
  */
37
6
 
38
- import { statPath, pathExists } from "@mailwoman/core/fs/readers"
7
+ import { statPath, pathExists } from "@mailwoman/core/fs/readers/stat"
39
8
  import { temporaryDirectory } from "@mailwoman/core/fs/temporary"
40
9
  import { copyFileTo, removePath } from "@mailwoman/core/fs/writers"
10
+ import { stringifyJSON } from "@mailwoman/core/json"
41
11
  import { countRows } from "@mailwoman/sqlite"
42
12
  import { DatabaseClient } from "@mailwoman/sqlite/client"
43
- import { sealDatabase } from "@mailwoman/sqlite/sealed-db"
13
+ import { sealDatabase } from "@mailwoman/sqlite/sealed/db"
44
14
  import { sql } from "kysely"
45
15
  import type { Kysely } from "kysely"
16
+ import type { PathBuilderLike } from "path-ts"
46
17
 
47
- import { buildPlaceSearchFTS, PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE, PLACE_SEARCH_TABLE } from "#fts/index"
18
+ import { buildPlaceSearchFTS, PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE, PLACE_SEARCH_TABLE } from "#fts"
48
19
  import type { NamesTable, SprTable, WOFDatabase } from "#schema"
49
20
 
50
21
  export interface BuildSlimOptions {
51
22
  /**
52
- * Input WOF SQLite distributions. Each should already have spr / names / place_population tables.
23
+ * Input WOF SQLite distributions, each already carrying spr / names / place_population tables.
53
24
  */
54
- inputs: string[]
25
+ inputs: readonly PathBuilderLike[]
55
26
  /**
56
- * Output path for the slim DB. Will be overwritten if it exists.
27
+ * Output path for the slim DB, overwritten if it exists.
57
28
  */
58
- output: string
29
+ output: PathBuilderLike
59
30
  /**
60
- * Country codes to keep (ISO 2-letter). Defaults to `["US"]`.
31
+ * Country codes to keep (ISO 2-letter); defaults to `["US"]`.
61
32
  */
62
33
  countries?: string[]
63
34
  /**
@@ -65,16 +36,11 @@ export interface BuildSlimOptions {
65
36
  */
66
37
  topLocalitiesPerCountry?: number
67
38
  /**
68
- * Drop the `names` table after the FTS index is built (default false). `place_search` is a self-contained FTS5 (no
69
- * external `content=`), so once it's built `names` is only the build-time source — the resolver queries
70
- * `place_search` + `spr` + `place_population` + `coincident_roles` and never reads `names` at runtime. Dropping it is
71
- * the single biggest size win (~2/3 of the file for a multi-locale build; see #359). A future consumer that needs raw
72
- * alt-names at runtime should ship a SEPARATE extract rather than re-bloat the hot DB.
39
+ * Drop the `names` table after the FTS index is built; `place_search` is self-contained
40
+ * and the resolver never reads `names` at runtime.
73
41
  */
74
42
  dropNames?: boolean
75
- /**
76
- * Optional progress callback for CLI / test introspection.
77
- */
43
+
78
44
  onProgress?: (phase: SlimBuildPhase, detail: string) => void
79
45
  }
80
46
 
@@ -112,7 +78,8 @@ export interface BuildSlimResult {
112
78
  const ANCESTOR_PLACETYPES = ["country", "region", "county", "borough", "macroregion"] as const
113
79
 
114
80
  /**
115
- * Tables copied verbatim (schema + filtered rows) from each source DB. Anything else is dropped.
81
+ * Tables copied verbatim (schema + filtered rows) from each source DB.
82
+ * Anything else is dropped.
116
83
  */
117
84
  const COPIED_TABLES = ["spr", "names", PLACE_POPULATION_TABLE] as const
118
85
 
@@ -121,19 +88,14 @@ const COPIED_TABLES = ["spr", "names", PLACE_POPULATION_TABLE] as const
121
88
  */
122
89
  const PLACE_POPULATION_DDL = `CREATE TABLE ${PLACE_POPULATION_TABLE} (id INTEGER PRIMARY KEY, population INTEGER NOT NULL DEFAULT 0)`
123
90
 
124
- /**
125
- * Minimal row shape for the population aux table — id + population, nothing else.
126
- */
127
91
  interface PlacePopulationTable {
128
92
  id: number
129
93
  population: number
130
94
  }
131
95
 
132
96
  /**
133
- * Kysely schema for the build phase. Mirrors the resolver-facing tables, plus the ATTACHed `src.*` tables so the
134
- * row-copying queries can name the source schema in `selectFrom` without falling back to raw SQL. ATTACH itself is
135
- * still raw — Kysely doesn't model it — but everything downstream (the SELECT-INSERT step that does the actual
136
- * filtering work) goes through the builder.
97
+ * Kysely schema for the build phase, including the ATTACHed `src.*` tables
98
+ * so row-copying queries can name the source schema.
137
99
  */
138
100
  interface BuildSchema {
139
101
  spr: SprTable
@@ -149,9 +111,8 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
149
111
  const topLocalities = opts.topLocalitiesPerCountry ?? 1000
150
112
  const progress = opts.onProgress ?? (() => {})
151
113
 
152
- // Callers pass `""` for extracts that don't exist yet (e.g. a not-yet-built custom postcode DB).
153
- // Skip empties up front; require every remaining path to exist.
154
- const inputs = opts.inputs.filter((p) => p.length > 0)
114
+ // An empty string marks an input that isn't built yet, so empties are skipped and every remaining path must exist.
115
+ const inputs = opts.inputs.map((input) => input.toString()).filter((p) => p.length)
155
116
 
156
117
  if (!inputs.length) throw new Error("no input WOF dbs provided")
157
118
 
@@ -165,10 +126,8 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
165
126
  await removePath(opts.output)
166
127
  }
167
128
 
168
- // Open the output DB and create the empty schema. We discover the schema from the FIRST input
169
- // (raw sqlite_master read — Kysely doesn't model that) so the output mirrors source column
170
- // ordering / types. `CREATE TABLE AS SELECT` flattens types to dynamic, which would break
171
- // callers that rely on column-affinity behavior.
129
+ // The output schema is replayed from the first input's sqlite_master so column ordering
130
+ // and affinity survive; `create table AS select` would flatten types to dynamic.
172
131
  const out = new DatabaseClient<BuildSchema>(opts.output)
173
132
  let result: BuildSlimResult
174
133
 
@@ -183,20 +142,18 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
183
142
  .get(table) as { sql?: string } | undefined
184
143
 
185
144
  if (createSQL?.sql) {
186
- // Raw DDL by design (introspect-and-replay): we exec the SOURCE DB's own CREATE TABLE
187
- // string read from sqlite_master, so a static Kysely builder can't express it. See AGENTS.md.
145
+ // Raw DDL by design: the source DB's own create-table string cannot be expressed by a static Kysely builder.
188
146
  out.exec(createSQL.sql)
189
147
  } else if (table === PLACE_POPULATION_TABLE) {
190
- // Older source builds may predate the aux table — create it empty so the per-source
191
- // copy + ranking have somewhere to land. Sparse-by-design; missing rows are fine.
148
+ // Older source builds may predate the aux table, so create it empty for the copy and ranking.
192
149
  out.exec(PLACE_POPULATION_DDL)
193
150
  } else {
194
151
  throw new Error(`source DB ${inputs[0]} is missing required table '${table}'`)
195
152
  }
196
153
  }
197
154
 
198
- // PRIMARY KEY on spr.id + place_population.id come from the schemas we copied; an explicit
199
- // index on names.id helps the per-id INSERT SELECT later.
155
+ // The copied schemas define the primary keys.
156
+ // This index on `names.id` helps the per-id insert select.
200
157
  out.exec(`CREATE INDEX IF NOT EXISTS names_id_idx ON names(id);`)
201
158
 
202
159
  // Pull rows from each input.
@@ -204,10 +161,8 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
204
161
  await copyFromSource(out, out, inputPath, countries, topLocalities, progress)
205
162
  }
206
163
 
207
- // Build the resolver virtual tables on the trimmed row set. Both place_search (FTS5) and
208
- // place_bbox (R*Tree) derive purely from spr + names — no geojson needed (see fts.ts). The
209
- // population aux table is NOT rebuilt here: it was copied verbatim above, and fts.ts only
210
- // (re)builds it when a `geojson` table is present, which the slim DB intentionally has not.
164
+ // `place_search` and `place_bbox` derive purely from `spr` + `names`, and the
165
+ // population aux table is not rebuilt because it was copied verbatim.
211
166
  progress("fts", "building place_search / place_bbox on slim DB")
212
167
 
213
168
  buildPlaceSearchFTS(out, {
@@ -215,34 +170,24 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
215
170
  onProgress: (phase, name) => progress("fts", `${phase} ${name}`),
216
171
  })
217
172
 
218
- // Materialize region/state ABBREVIATIONS into a standalone `place_abbr (id, abbr)` table BEFORE
219
- // `names` is (optionally) dropped. The full DB lets the resolver tier an exact-abbrev match by
220
- // querying `names` (`#exactMatchIDs`), but the slim DB drops `names` for size — so the
221
- // browser resolver gets its own tiny lookup (~hundreds of rows) to do the same data-driven
222
- // exact-abbrev tiering ("VT" → Vermont, not a token-matching foreign region) instead of the
223
- // demo's hardcoded region-abbreviation map (since deleted). Sourced from the `language='abbr'` rows
224
- // `add-region-abbrevs.ts` wrote, already filtered to surviving spr ids via the names copy. The
225
- // table is always created (empty when the source predates the abbrev enrichment) so the
226
- // resolver can query it unconditionally.
173
+ // `place_abbr` is materialized before `names` is dropped and always created,
174
+ // even empty, so the resolver can query it unconditionally.
227
175
  progress("place_abbr", "materializing region abbreviations")
228
176
  out.exec(`CREATE TABLE IF NOT EXISTS place_abbr (id INTEGER NOT NULL, abbr TEXT NOT NULL)`)
229
177
  out.exec(`INSERT INTO place_abbr (id, abbr) SELECT id, name FROM names WHERE language = 'abbr'`)
230
178
  out.exec(`CREATE INDEX IF NOT EXISTS place_abbr_by_abbr ON place_abbr (abbr COLLATE NOCASE)`)
231
179
  out.exec(`CREATE INDEX IF NOT EXISTS place_abbr_by_id ON place_abbr (id)`)
232
180
 
233
- // Capture the names count BEFORE any drop so the build report stays informative.
234
181
  const namesRows = countRows(out, "names")
235
182
 
236
- // Optionally drop `names` (+ its index) now that the self-contained FTS5 index no longer needs
237
- // it. The resolver never reads `names` at query time, so this is pure size reduction.
183
+ // The resolver never reads `names` at query time, so dropping it and its index is pure size reduction.
238
184
  if (opts.dropNames) {
239
185
  progress("vacuum", `dropping names table (${namesRows} rows; FTS5 is self-contained)`)
240
186
  out.exec(`DROP INDEX IF EXISTS names_id_idx;`)
241
187
  out.exec(`DROP TABLE IF EXISTS names;`)
242
188
  }
243
189
 
244
- // VACUUM the output so the on-disk file reflects just the trimmed row count. Without it the
245
- // file size stays inflated from the in-flight INSERT churn.
190
+ // Vacuum so the on-disk file reflects the trimmed rows rather than the in-flight insert churn.
246
191
  progress("vacuum", "VACUUM (final size reduction)")
247
192
  out.exec("VACUUM;")
248
193
 
@@ -254,10 +199,10 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
254
199
  placePopulation: countRows(out, PLACE_POPULATION_TABLE),
255
200
  }
256
201
 
257
- progress("done", JSON.stringify(rowCounts))
202
+ progress("done", stringifyJSON(rowCounts))
258
203
 
259
204
  result = {
260
- outputPath: opts.output,
205
+ outputPath: opts.output.toString(),
261
206
  outputBytes: (await statPath(opts.output)).size,
262
207
  rowCounts,
263
208
  }
@@ -279,32 +224,24 @@ async function copyFromSource(
279
224
  topLocalities: number,
280
225
  progress: NonNullable<BuildSlimOptions["onProgress"]>
281
226
  ): Promise<void> {
282
- // ATTACH avoids any "load source into memory" step — SQLite walks both files in place. We need
283
- // a fresh temp copy because some WOF distributions ship as read-only filesystem mounts and
284
- // ATTACH will still want a writable journal on the side; copying to /tmp dodges that without
285
- // mutating the canonical files in /mnt/playpen/mailwoman-data/wof/. ATTACH / DETACH stay raw
286
- // — Kysely doesn't model them.
227
+ // A fresh scratch copy is attached so read-only WOF distributions get the writable
228
+ // journal they need without mutating the canonical files.
287
229
  await using tmpScratch = await temporaryDirectory("mailwoman-slim-src-")
288
- const scratchPath = tmpScratch.resolve("src.db")
230
+ const scratchPath = tmpScratch.path("src.db")
289
231
 
290
232
  await copyFileTo(inputPath, scratchPath)
291
233
 
292
- out.exec(`ATTACH DATABASE '${scratchPath.replaceAll("'", "''")}' AS src;`)
234
+ out.exec(`ATTACH DATABASE '${scratchPath.toString().replaceAll("'", "''")}' AS src;`)
293
235
 
294
236
  try {
295
- // Does this extract carry the pre-built population aux table? The admin source does; a bare
296
- // postcode extract might not. The locality ranking + population copy below adapt accordingly.
297
237
  const srcHasPopulation = Boolean(
298
238
  out.prepare(`SELECT 1 FROM src.sqlite_master WHERE type = 'table' AND name = '${PLACE_POPULATION_TABLE}'`).get()
299
239
  )
300
240
 
301
- // The SELECT-INSERT queries below go through Kysely. The cross-schema FROM is the only
302
- // "interesting" bit: by declaring `src.spr` / `src.names` / `src.place_population` in
303
- // `BuildSchema`, Kysely lets us write `selectFrom("src.spr")` with the same column-type
304
- // checking as the regular schema. SQLite parses the dotted identifier as a schema-name
305
- // qualifier, so this works directly without any aliasing trick.
241
+ // The `src.spr` declaration and its peers in `BuildSchema` let Kysely
242
+ // column-check the cross-schema select.
243
+ // SQLite reads the dotted identifier as a schema qualifier.
306
244
 
307
- // 1. Ancestor placetypes (country / region / county / etc.) — always-kept.
308
245
  progress("country", `${inputPath}: ancestor placetypes in (${countries.join(",")})`)
309
246
 
310
247
  await kysely
@@ -321,9 +258,8 @@ async function copyFromSource(
321
258
  .onConflict((oc) => oc.doNothing())
322
259
  .execute()
323
260
 
324
- // 2. Top-K localities by population. Population lives in the pre-built `place_population`
325
- // aux table — left-join it so localities without a population row still qualify (sorted
326
- // last). If the extract has no population table, fall back to a deterministic id ordering.
261
+ // Localities without a population row still qualify.
262
+ // Without the aux table, fall back to a deterministic id ordering.
327
263
  progress("locality", `${inputPath}: top-${topLocalities} localities by population`)
328
264
 
329
265
  await kysely
@@ -343,7 +279,6 @@ async function copyFromSource(
343
279
  .onConflict((oc) => oc.doNothing())
344
280
  .execute()
345
281
 
346
- // 3. All postcodes in scope.
347
282
  progress("postcode", `${inputPath}: all postcodes`)
348
283
 
349
284
  await kysely
@@ -360,7 +295,6 @@ async function copyFromSource(
360
295
  .onConflict((oc) => oc.doNothing())
361
296
  .execute()
362
297
 
363
- // 4. Pull names for the IDs we just selected.
364
298
  progress("names", `${inputPath}: names rows for selected IDs`)
365
299
 
366
300
  await kysely
@@ -369,7 +303,6 @@ async function copyFromSource(
369
303
  .onConflict((oc) => oc.doNothing())
370
304
  .execute()
371
305
 
372
- // 5. Pull population rows for the selected IDs (sparse — only the places WOF has a count for).
373
306
  if (srcHasPopulation) {
374
307
  progress("place_population", `${inputPath}: population rows for selected IDs`)
375
308
 
@@ -382,11 +315,8 @@ async function copyFromSource(
382
315
  .execute()
383
316
  }
384
317
 
385
- // 6. Carry the coincident_roles relation (#402) when this source has it (the admin DB), so the
386
- // slim/demo DB supports dual-role hierarchy completion (on by default). Filtered to surviving
387
- // spr ids → no orphans. Tiny (~hundreds of rows). `ancestors` is intentionally NOT copied (huge
388
- // + build-only), so we copy the derived table rather than rebuild it. Raw SQL — conditional +
389
- // not in the Kysely build schema.
318
+ // `ancestors` is intentionally not copied, so the derived `coincident_roles`
319
+ // table is copied and filtered to surviving spr ids.
390
320
  const relationSchema = out
391
321
  .prepare(`SELECT sql FROM src.sqlite_master WHERE type = 'table' AND name = 'coincident_roles'`)
392
322
  .get() as { sql?: string } | undefined
@@ -2,7 +2,7 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
- * @file Pass 2 of the candidate build — explode `place_search.alt_names` into distinct alias rows.
5
+ * @file Pass 2 of the candidate build: explode `place_search.alt_names` into distinct alias rows.
6
6
  */
7
7
 
8
8
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
@@ -10,19 +10,18 @@ import { tableExists } from "@mailwoman/sqlite/introspection"
10
10
 
11
11
  import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
12
12
  import type { CandidateDatabase } from "#candidate/schema"
13
- import { ALIAS_SEPARATOR } from "#fts/index"
13
+ import { ALIAS_SEPARATOR } from "#fts"
14
14
  import type { WOFDatabase } from "#schema"
15
15
  import { normalizeLocalityForKey } from "#street/normalize"
16
16
 
17
17
  /**
18
- * The official name of every current region, keyed `${country id}\0${name key}` → region id, from the source `names`
19
- * table's `official` bit. Empty when the source carries no `names` table (a fixture, or an extract shape).
18
+ * The official name of every current region, keyed `${country id}\0${name key}` →
19
+ * region id, from the source `names` table's `official` bit.
20
20
  *
21
- * This is the positive evidence behind the one alias refusal below: a name is refused from a region's bag ONLY when
22
- * another region of the same country holds it as its official name. Three such pairs exist in the admin artifact —
23
- * `新竹市` on Hsinchu County (the city's official name), `嘉義市` on Chiayi County, `충청남도` on Sejong — and each let the more
24
- * populous holder of the variant outrank the place the name officially is, so a `新竹市` region node resolved the county
25
- * and every district scoped to the city contradicted it.
21
+ * Empty when the source has no `names` table (a fixture, or an extract shape).
22
+ *
23
+ * The positive evidence behind the alias refusal below: a name is refused from a region's
24
+ * bag only when another region of the same country holds it as its official name.
26
25
  */
27
26
  function officialNameHoldersByRegion(
28
27
  src: DatabaseClient<WOFDatabase>,
@@ -50,12 +49,12 @@ function officialNameHoldersByRegion(
50
49
  }
51
50
 
52
51
  /**
53
- * Pass 2 — explode each place's `place_search.alt_names` bag into distinct-key alias rows (`is_primary = 0`), and count
54
- * each place's distinct staged keys (primary included) — the gloss detector's key-count signal (#1730).
52
+ * Pass 2: explode each place's `place_search.alt_names` bag into distinct-key alias rows
53
+ * (`is_primary = 0`), and count each place's distinct staged keys with the primary included.
55
54
  *
56
- * With `regionPlacetypeID` and `ccID` given, a REGION's alias that is another same-country region's official name is
57
- * refused and counted rather than staged (see {@link officialNameHoldersByRegion}); without them the pass stages every
58
- * alias.
55
+ * With `regionPlacetypeID` and `ccID` given, a region's alias that is another same-country region's
56
+ * official name is refused and counted rather than staged (see {@link officialNameHoldersByRegion}).
57
+ * Without them the pass stages every alias.
59
58
  */
60
59
  export function explodeAliasBags(
61
60
  src: DatabaseClient<WOFDatabase>,
@@ -84,9 +83,9 @@ export function explodeAliasBags(
84
83
  const seen = new Set<string>([a.pkey])
85
84
  const isRegion = a.ptid === opts.regionPlacetypeID
86
85
 
87
- // The writer space-pads each separator and appends a trailing one, so every piece arrives with
88
- // surrounding whitespace and the last one is empty. `normalizeLocalityForKey` folds both away,
89
- // and the empty tail falls out at the `!k` guard below.
86
+ // The writer space-pads each separator and appends a trailing one, so every piece
87
+ // includes surrounding whitespace and the last one is empty.
88
+ // `normalizeLocalityForKey` folds both away.
90
89
  for (const piece of alt.split(ALIAS_SEPARATOR)) {
91
90
  const k = normalizeLocalityForKey(piece)
92
91
 
@@ -3,80 +3,78 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Typed schema for the candidate gazetteer's ANCESTORS sidecar (`candidate_ancestor` +
6
+ * Typed schema for the candidate gazetteer's ancestors sidecar (`candidate_ancestor` +
7
7
  * `candidate_interval`) — the containment lineage the candidate table itself cannot answer, built
8
8
  * into the same `candidate.db` by `build-candidate.ts` and read by
9
9
  * {@link WOFCandidateTableLookup.ancestors}.
10
10
  *
11
- * ENCODING: closure-lite rows, denormalized — one row per (place, ancestor) edge carrying the
12
- * parent's placetype, display name and folded key — rather than a fixed-slot id chain on the
13
- * candidate row. Decided by the two consumers:
11
+ * The sidecar stores denormalized closure rows. Each row represents one (place, ancestor) edge. The row includes the
12
+ * parent's placetype, display name and folded key. The two consumers determine this layout:
14
13
  *
15
- * 1. The admin-coherence check needs the WINNER's chain as (placetype, name) pairs in one probe.
16
- * A fixed-slot `[id;8]` chain answers with ids, and every id then needs a name lookup the
17
- * artifact has no per-id table for — up to 8 indirections where the closure row has zero.
18
- * 2. The account layer needs EVERY candidate under a `name_key` enumerable WITH its chain from
14
+ * 1. The admin-coherence check needs the winner's chain as (placetype, name) pairs in one probe.
15
+ * A fixed-slot `[id.8]` chain returns ids. Each id would then need a name lookup, but the artifact has no
16
+ * per-id table. That design requires up to 8 indirections. The closure row requires none.
17
+ * 2. The account layer needs every candidate under a `name_key` enumerable with its chain from
19
18
  * one artifact probe ("present-but-outranked, discriminated by containment"). That is the
20
19
  * candidate probe (contiguous) followed by one `spr_id`-clustered closure probe per candidate
21
20
  * — each a handful of adjacent pages.
22
21
  *
23
- * Denormalizing the parent onto the edge is the same discipline as the candidate table itself:
24
- * this artifact is read over HTTP byte ranges, where a join to a dimension table scatters page
25
- * fetches, and a `WITHOUT ROWID` B-tree clustered on `(spr_id, depth)` keeps a whole chain in
26
- * 1-2 pages. The repeated parent strings are the price of the zero-join read, paid at build time.
22
+ * The artifact is read over HTTP byte ranges. A join to a dimension table scatters page fetches. A `without rowid`
23
+ * B-tree clustered on `(spr_id, depth)` keeps a whole chain in 1–2 pages. The builder repeats parent strings to
24
+ * avoid joins during reads.
27
25
  *
28
- * `depth` is 1 for the NEAREST ancestor (deepest containment tier), increasing outward to the
29
- * country — the same nearest-first order `ancestry.ts` serves for the FTS backend, so the two
30
- * backends' `ancestors()` agree by construction. The order within a place is deterministic:
26
+ * `depth` is 1 for the nearest ancestor (deepest containment tier) and increases outward to the
27
+ * country. This matches the nearest-first order that `ancestry.ts` serves for the FTS backend. Both backends'
28
+ * `ancestors()` results therefore use the same order. The order within a place is deterministic:
31
29
  * containment depth descending (`placetypeDepth`), then ancestor id ascending, capped at
32
- * {@link MAX_ANCESTOR_DEPTH}. `parent_name_key` is the SHARED {@link normalizeLocalityForKey}
30
+ * {@link MAX_ANCESTOR_DEPTH}. `parent_name_key` is the shared {@link normalizeLocalityForKey}
33
31
  * fold — the same fold `candidate.name_key` is built with, so a chain entry and a candidate key
34
32
  * compare under one normalizer.
35
33
  *
36
- * `candidate_interval` carries pre/post-order labels over the CANONICAL-PARENT FOREST, assigned
37
- * at build time: `a` contains `d` ⟺ `a.pre <= d.pre AND d.post <= a.post` — O(1) in either
38
- * direction with no chain scan and no knowledge of either side's tier — and the descendants of
39
- * `a` are the contiguous range `pre BETWEEN a.pre AND a.post`. Interval labels are classically
40
- * avoided for their relabel-on-update cost; this database is a sealed read-only artifact rebuilt
41
- * whole, which is exactly the regime where that cost is void.
42
- *
43
- * THE DAG CAVEAT, and the recorded choice: WOF places can carry more than one parent (multiple
44
- * hierarchies, ambiguous boundaries). `candidate_ancestor` keeps EVERY parent — the closure rows
45
- * are the complete containment record. A single interval pair can only encode a tree, so the
46
- * interval forest links each place to ONE canonical parent: its depth-1 edge — the finest
47
- * containment tier, lowest ancestor id — the same MIN-stability convention the candidate table's
48
- * `region_id` stamp uses. A containment question about a NON-canonical hierarchy must consult the
49
- * closure rows; the interval answer for it is `false`, which is why interval verdicts are
50
- * "contained along the canonical hierarchy", never "not contained at all".
51
- *
52
- * ABSENCE SEMANTICS (meaning-of-zero): a place with no `candidate_interval` row has no recorded
34
+ * `candidate_interval` stores pre/post-order labels over the canonical-parent forest. The builder assigns them
35
+ * during construction. The predicate `a.pre <= d.pre and d.post <= a.post` tests whether `a` contains `d` in O(1)
36
+ * time. It needs no chain scan or tier information. Descendants of `a` occupy the contiguous range
37
+ * `pre between a.pre and a.post`. Interval labels add relabeling work to updates. This database is sealed and
38
+ * read-only, so the builder can regenerate all labels together.
39
+ *
40
+ * WOF places can have multiple parents because boundaries can be ambiguous or participate in multiple
41
+ * hierarchies. `candidate_ancestor` stores every parent. Its closure rows hold the complete containment record.
42
+ * One interval pair encodes only a tree. The interval forest therefore links each place to one canonical parent:
43
+ * the depth-1 edge with the finest containment tier and lowest ancestor id. This follows the same MIN-based
44
+ * stability convention as the candidate table's `region_id` stamp. A containment query about another hierarchy
45
+ * must consult the closure rows. The interval result answers only whether a place is contained along the canonical
46
+ * hierarchy.
47
+ *
48
+ * absence semantics (meaning-of-zero): a place with no `candidate_interval` row has no recorded
53
49
  * ancestry in the source (extract-fed postcodes and localities, isolated places, cycle-skipped
54
- * rows). Absence is UNVERIFIABLE, never a containment verdict.
50
+ * rows). Absence is unverifiable, never a containment verdict.
55
51
  */
56
52
 
57
53
  import { sql, type Kysely } from "kysely"
58
54
 
59
- // Type-only and circular on purpose (candidate-schema extends CandidateAncestorsDatabase): Kysely's
60
- // DB parameter is invariant, so the DDL functions must take the FULL database type their caller
61
- // holds. Erased at runtime.
55
+ // Type-only and circular on purpose (candidate-schema extends CandidateAncestorsDatabase): Kysely's DB
56
+ // parameter is invariant, so the DDL functions must take the full database type their caller holds.
57
+ // Erased at runtime.
62
58
  import type { CandidateDatabase } from "#candidate/schema"
63
59
  import type { NameKey } from "#street/normalize"
64
60
 
65
61
  /**
66
- * The deepest chain the sidecar stores per place. WOF containment within the resolvable placetypes (country …
67
- * microhood) never legitimately exceeds this; anything past it is source noise the build drops (and counts) rather than
68
- * stores.
62
+ * The deepest chain the sidecar stores per place.
63
+ *
64
+ * WOF containment within the resolvable placetypes (country … microhood) never legitimately exceeds this.
65
+ * The build drops and counts anything past it as source noise rather than storing it.
69
66
  */
70
67
  export const MAX_ANCESTOR_DEPTH = 8
71
68
 
72
69
  /**
73
- * The closure-row table's name — probed with `hasTable` by the reader, so an artifact predating the sidecar degrades to
74
- * "no ancestors capability" rather than `no such table`.
70
+ * The closure-row table's name — probed with `hasTable` by the reader, so an artifact
71
+ * predating the sidecar degrades to "no ancestors capability" rather than `no such table`.
75
72
  */
76
73
  export const CANDIDATE_ANCESTOR_TABLE = "candidate_ancestor"
77
74
 
78
75
  /**
79
- * The interval-label table's name — the closure table's seal-time sibling; existence-restricted the same way.
76
+ * The interval-label table's name — the closure table's seal-time sibling.
77
+ * Existence-restricted the same way.
80
78
  */
81
79
  export const CANDIDATE_INTERVAL_TABLE = "candidate_interval"
82
80
 
@@ -102,19 +100,24 @@ export interface CandidateAncestorTable {
102
100
  */
103
101
  parent_placetype_id: number
104
102
  /**
105
- * The ancestor's canonical display name — what {@link Ancestor.name} serves, matching the FTS backend's register.
103
+ * The ancestor's canonical display name.
104
+ *
105
+ * What {@link Ancestor.name} serves, matching the FTS backend's register.
106
106
  */
107
107
  parent_name: string
108
108
  /**
109
- * The SHARED {@link normalizeLocalityForKey} fold of `parent_name` — comparable against `candidate.name_key` and
110
- * against a query-side fold under one normalizer, by construction.
109
+ * The shared {@link normalizeLocalityForKey} fold of `parent_name` — comparable against
110
+ * `candidate.name_key` and against a query-side fold under one normalizer, by construction.
111
111
  */
112
112
  parent_name_key: NameKey
113
113
  }
114
114
 
115
115
  /**
116
- * Pre/post-order labels over the canonical-parent forest — one row per place WITH recorded ancestry (see the module
117
- * docstring for absence semantics). `pre < post` always; labels are unique across the artifact.
116
+ * Pre/post-order labels over the canonical-parent forest — one row per place with
117
+ * recorded ancestry (see the module docstring for absence semantics).
118
+ *
119
+ * `pre < post` always.
120
+ * Labels are unique across the artifact.
118
121
  */
119
122
  export interface CandidateIntervalTable {
120
123
  spr_id: number
@@ -123,8 +126,10 @@ export interface CandidateIntervalTable {
123
126
  }
124
127
 
125
128
  /**
126
- * The sidecar tables, for a `Kysely` view over the candidate DB. `CandidateDatabase` (candidate-schema.ts) extends
127
- * this, so the builder's one typed client sees both families.
129
+ * The sidecar tables, for a `Kysely` view over the candidate DB.
130
+ *
131
+ * `CandidateDatabase` (candidate-schema.ts) extends this, so the builder's
132
+ * one typed client sees both families.
128
133
  */
129
134
  export interface CandidateAncestorsDatabase {
130
135
  candidate_ancestor: CandidateAncestorTable
@@ -132,8 +137,12 @@ export interface CandidateAncestorsDatabase {
132
137
  }
133
138
 
134
139
  /**
135
- * The `candidate_ancestor` columns in clustered-key order — the first two ARE the primary key, and the builder's
136
- * positional `INSERT` binds by this order. Keep in sync with {@link CandidateAncestorTable}.
140
+ * The `candidate_ancestor` columns in clustered-key order.
141
+ *
142
+ * The first two columns form the primary key.
143
+ * The builder's positional `insert` binds values in this order.
144
+ *
145
+ * Keep in sync with {@link CandidateAncestorTable}.
137
146
  */
138
147
  export const CANDIDATE_ANCESTOR_COLUMNS = [
139
148
  "spr_id",
@@ -145,8 +154,10 @@ export const CANDIDATE_ANCESTOR_COLUMNS = [
145
154
  ] as const
146
155
 
147
156
  /**
148
- * Create the clustered closure table. The builder inserts in `(spr_id, depth)` order so the B-tree leaves are
149
- * contiguous per place — the byte-range read discipline.
157
+ * Create the clustered closure table.
158
+ *
159
+ * The builder inserts in `(spr_id, depth)` order so the B-tree leaves are contiguous
160
+ * per place — the byte-range read discipline.
150
161
  */
151
162
  export async function createCandidateAncestorTable(db: Kysely<CandidateDatabase>): Promise<void> {
152
163
  await db.schema
@@ -158,13 +169,15 @@ export async function createCandidateAncestorTable(db: Kysely<CandidateDatabase>
158
169
  .addColumn("parent_name", "text", (c) => c.notNull())
159
170
  .addColumn("parent_name_key", "text", (c) => c.notNull())
160
171
  .addPrimaryKeyConstraint("candidate_ancestor_pk", ["spr_id", "depth"])
161
- // `WITHOUT ROWID` has no first-class builder; the raw modifier is the idiomatic fallback.
172
+ // `without rowid` has no first-class builder. The raw modifier is the idiomatic fallback.
162
173
  .modifyEnd(sql`without rowid`)
163
174
  .execute()
164
175
  }
165
176
 
166
177
  /**
167
- * Create the interval-label table. Small rows probed by primary key — the `WITHOUT ROWID` win case.
178
+ * Create the interval-label table.
179
+ *
180
+ * Small rows probed by primary key — the `without rowid` win case.
168
181
  */
169
182
  export async function createCandidateIntervalTable(db: Kysely<CandidateDatabase>): Promise<void> {
170
183
  await db.schema
@@ -177,7 +190,8 @@ export async function createCandidateIntervalTable(db: Kysely<CandidateDatabase>
177
190
  }
178
191
 
179
192
  /**
180
- * One place's interval label — the shape both sides of a containment comparison read.
193
+ * One place's interval label.
194
+ * The shape both sides of a containment comparison read.
181
195
  */
182
196
  export interface IntervalLabel {
183
197
  pre: number
@@ -185,10 +199,12 @@ export interface IntervalLabel {
185
199
  }
186
200
 
187
201
  /**
188
- * Does `outer` contain `inner` along the canonical hierarchy? Reflexive: a place contains itself (containment
189
- * degenerates to identity, the same reading the admin-coherence check gives a self-match). The shared FUNCTION for
190
- * every consumer of the labels — a re-derived comparison risks disagreeing at exactly the strict/inclusive boundary
191
- * this line settles.
202
+ * Does `outer` contain `inner` along the canonical hierarchy?
203
+ *
204
+ * Reflexive: a place contains itself (containment degenerates to identity,
205
+ * the same reading the admin-coherence check gives a self-match).
206
+ * The shared function for every consumer of the labels — a re-derived comparison risks
207
+ * disagreeing at exactly the strict/inclusive boundary this line settles.
192
208
  */
193
209
  export function intervalContains(outer: IntervalLabel, inner: IntervalLabel): boolean {
194
210
  return outer.pre <= inner.pre && inner.post <= outer.post