@mailwoman/resolver-wof-sqlite 9.1.0 → 9.3.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 (501) hide show
  1. package/README.md +36 -20
  2. package/lib/address/index.ts +9 -0
  3. package/{address-point-interpolation.ts → lib/address/point-interpolation.ts} +40 -28
  4. package/{address-point-schema.ts → lib/address/point-schema.ts} +34 -8
  5. package/lib/address/point.ts +280 -0
  6. package/{ancestry-backfill.ts → lib/ancestry/backfill.ts} +14 -19
  7. package/{ancestry.ts → lib/ancestry/index.ts} +13 -8
  8. package/{build-candidate.ts → lib/build-candidate.ts} +243 -199
  9. package/{build-slim.ts → lib/build-slim.ts} +153 -168
  10. package/lib/candidate/alias-bags.ts +56 -0
  11. package/lib/candidate/ancestors-sidecar.ts +204 -0
  12. package/lib/candidate/country-display-names.ts +79 -0
  13. package/lib/candidate/extract-fold.ts +138 -0
  14. package/lib/candidate/name-roles.ts +238 -0
  15. package/lib/candidate/own-name.ts +146 -0
  16. package/lib/candidate/place-attrs.ts +44 -0
  17. package/lib/candidate-ancestors-schema.ts +195 -0
  18. package/{candidate-fts.ts → lib/candidate-fts.ts} +7 -5
  19. package/{candidate-importance.ts → lib/candidate-importance.ts} +54 -57
  20. package/lib/candidate-lookup.ts +976 -0
  21. package/{candidate-schema.ts → lib/candidate-schema.ts} +34 -6
  22. package/lib/candidate-scoring.ts +261 -0
  23. package/lib/capital-schema.ts +89 -0
  24. package/lib/capitals.ts +148 -0
  25. package/{coincident-roles.ts → lib/coincident-roles.ts} +75 -19
  26. package/{convention.ts → lib/convention/index.ts} +5 -5
  27. package/lib/convention/schema.ts +72 -0
  28. package/{coverage-manifest-schema.ts → lib/coverage-manifest-schema.ts} +15 -16
  29. package/lib/currency-backfill.ts +334 -0
  30. package/lib/env.ts +50 -0
  31. package/lib/exact-match.ts +113 -0
  32. package/{sharding.ts → lib/extracts.ts} +63 -60
  33. package/lib/fst/autocomplete.ts +176 -0
  34. package/{fst-builder.ts → lib/fst/builder.ts} +30 -27
  35. package/{fst-deserialize-web.ts → lib/fst/deserialize-web.ts} +27 -87
  36. package/lib/fst/format.ts +106 -0
  37. package/{fst-freshness.ts → lib/fst/freshness.ts} +42 -75
  38. package/lib/fst/index.ts +14 -0
  39. package/{fst-matcher.ts → lib/fst/matcher.ts} +1 -1
  40. package/{fst-serialize.ts → lib/fst/serialize.ts} +37 -115
  41. package/{fst-types.ts → lib/fst/types.ts} +4 -2
  42. package/{fts.ts → lib/fts/index.ts} +28 -23
  43. package/{fts-query.ts → lib/fts/query.ts} +2 -2
  44. package/{geonames-aliases.ts → lib/geonames/aliases.ts} +13 -13
  45. package/lib/geonames/index.ts +8 -0
  46. package/{geonames-postal.ts → lib/geonames/postal.ts} +16 -15
  47. package/{index.ts → lib/index.ts} +52 -48
  48. package/{interpolation.ts → lib/interpolation.ts} +137 -41
  49. package/lib/lookup.ts +861 -0
  50. package/{name-score.ts → lib/name-score.ts} +6 -4
  51. package/lib/nsul/index.ts +8 -0
  52. package/lib/nsul/lookup.ts +144 -0
  53. package/lib/nsul/schema.ts +130 -0
  54. package/{place-importance-schema.ts → lib/place-importance-schema.ts} +70 -22
  55. package/lib/poi/index.ts +8 -0
  56. package/{poi-lookup.ts → lib/poi/lookup.ts} +32 -39
  57. package/{poi-schema.ts → lib/poi/schema.ts} +10 -6
  58. package/lib/polygon-schema.ts +47 -0
  59. package/{postal-city-alias-lookup.ts → lib/postal/city-alias-lookup.ts} +15 -21
  60. package/{postal-city-candidate-schema.ts → lib/postal/city-candidate-schema.ts} +4 -2
  61. package/lib/postal/index.ts +9 -0
  62. package/{postcode-point-lookup.ts → lib/postcode-point-lookup.ts} +17 -15
  63. package/lib/primary-preference.ts +226 -0
  64. package/lib/proximity-rerank.ts +120 -0
  65. package/{ranking-weights.ts → lib/ranking-weights.ts} +30 -4
  66. package/lib/region-keys.ts +144 -0
  67. package/{reverse.ts → lib/reverse.ts} +45 -56
  68. package/{schema.ts → lib/schema.ts} +11 -1
  69. package/lib/search-fetch.ts +257 -0
  70. package/{sqlite-convention-source.ts → lib/sqlite-convention-source.ts} +9 -10
  71. package/lib/sqlite-utils.ts +83 -0
  72. package/{street-centroid-schema.ts → lib/street/centroid-schema.ts} +12 -6
  73. package/{street-centroid.ts → lib/street/centroid.ts} +26 -28
  74. package/lib/street/index.ts +13 -0
  75. package/{street-morphology-fst-builder.ts → lib/street/morphology-fst-builder.ts} +24 -14
  76. package/{street-morphology-fst-loader.ts → lib/street/morphology-fst-loader.ts} +14 -12
  77. package/{street-name-lookup.ts → lib/street/name-lookup.ts} +14 -30
  78. package/lib/street/normalize.ts +572 -0
  79. package/{street-segment-schema.ts → lib/street/segment-schema.ts} +13 -8
  80. package/{types.ts → lib/types.ts} +49 -9
  81. package/{unified-schema.ts → lib/unified-schema.ts} +21 -25
  82. package/lib/uprn/existence.ts +84 -0
  83. package/lib/uprn/index.ts +9 -0
  84. package/lib/uprn/lookup.ts +206 -0
  85. package/lib/uprn/schema.ts +124 -0
  86. package/lib/weights-overlay-linker.ts +878 -0
  87. package/out/address/index.d.ts +9 -0
  88. package/out/address/index.d.ts.map +1 -0
  89. package/out/address/index.js +9 -0
  90. package/out/address/index.js.map +1 -0
  91. package/out/{address-point-interpolation.d.ts → address/point-interpolation.d.ts} +11 -10
  92. package/out/address/point-interpolation.d.ts.map +1 -0
  93. package/out/{address-point-interpolation.js → address/point-interpolation.js} +29 -23
  94. package/out/address/point-interpolation.js.map +1 -0
  95. package/out/{address-point-schema.d.ts → address/point-schema.d.ts} +30 -10
  96. package/out/address/point-schema.d.ts.map +1 -0
  97. package/out/{address-point-schema.js → address/point-schema.js} +7 -3
  98. package/out/address/point-schema.js.map +1 -0
  99. package/out/{address-point.d.ts → address/point.d.ts} +16 -8
  100. package/out/address/point.d.ts.map +1 -0
  101. package/out/address/point.js +191 -0
  102. package/out/address/point.js.map +1 -0
  103. package/out/{ancestry-backfill.d.ts → ancestry/backfill.d.ts} +7 -5
  104. package/out/ancestry/backfill.d.ts.map +1 -0
  105. package/out/{ancestry-backfill.js → ancestry/backfill.js} +12 -16
  106. package/out/ancestry/backfill.js.map +1 -0
  107. package/out/{ancestry.d.ts → ancestry/index.d.ts} +6 -5
  108. package/out/ancestry/index.d.ts.map +1 -0
  109. package/out/{ancestry.js → ancestry/index.js} +7 -7
  110. package/out/ancestry/index.js.map +1 -0
  111. package/out/build-candidate.d.ts +88 -7
  112. package/out/build-candidate.d.ts.map +1 -1
  113. package/out/build-candidate.js +145 -149
  114. package/out/build-candidate.js.map +1 -1
  115. package/out/build-slim.d.ts +5 -5
  116. package/out/build-slim.d.ts.map +1 -1
  117. package/out/build-slim.js +127 -142
  118. package/out/build-slim.js.map +1 -1
  119. package/out/candidate/alias-bags.d.ts +19 -0
  120. package/out/candidate/alias-bags.d.ts.map +1 -0
  121. package/out/candidate/alias-bags.js +39 -0
  122. package/out/candidate/alias-bags.js.map +1 -0
  123. package/out/candidate/ancestors-sidecar.d.ts +32 -0
  124. package/out/candidate/ancestors-sidecar.d.ts.map +1 -0
  125. package/out/candidate/ancestors-sidecar.js +140 -0
  126. package/out/candidate/ancestors-sidecar.js.map +1 -0
  127. package/out/candidate/country-display-names.d.ts +35 -0
  128. package/out/candidate/country-display-names.d.ts.map +1 -0
  129. package/out/candidate/country-display-names.js +59 -0
  130. package/out/candidate/country-display-names.js.map +1 -0
  131. package/out/candidate/extract-fold.d.ts +32 -0
  132. package/out/candidate/extract-fold.d.ts.map +1 -0
  133. package/out/candidate/extract-fold.js +104 -0
  134. package/out/candidate/extract-fold.js.map +1 -0
  135. package/out/candidate/name-roles.d.ts +57 -0
  136. package/out/candidate/name-roles.d.ts.map +1 -0
  137. package/out/candidate/name-roles.js +166 -0
  138. package/out/candidate/name-roles.js.map +1 -0
  139. package/out/candidate/own-name.d.ts +50 -0
  140. package/out/candidate/own-name.d.ts.map +1 -0
  141. package/out/candidate/own-name.js +132 -0
  142. package/out/candidate/own-name.js.map +1 -0
  143. package/out/candidate/place-attrs.d.ts +43 -0
  144. package/out/candidate/place-attrs.d.ts.map +1 -0
  145. package/out/candidate/place-attrs.js +15 -0
  146. package/out/candidate/place-attrs.js.map +1 -0
  147. package/out/candidate-ancestors-schema.d.ts +150 -0
  148. package/out/candidate-ancestors-schema.d.ts.map +1 -0
  149. package/out/candidate-ancestors-schema.js +123 -0
  150. package/out/candidate-ancestors-schema.js.map +1 -0
  151. package/out/candidate-fts.d.ts +7 -5
  152. package/out/candidate-fts.d.ts.map +1 -1
  153. package/out/candidate-fts.js +5 -3
  154. package/out/candidate-fts.js.map +1 -1
  155. package/out/candidate-importance.d.ts +16 -16
  156. package/out/candidate-importance.d.ts.map +1 -1
  157. package/out/candidate-importance.js +47 -52
  158. package/out/candidate-importance.js.map +1 -1
  159. package/out/candidate-lookup.d.ts +31 -52
  160. package/out/candidate-lookup.d.ts.map +1 -1
  161. package/out/candidate-lookup.js +380 -172
  162. package/out/candidate-lookup.js.map +1 -1
  163. package/out/candidate-schema.d.ts +31 -7
  164. package/out/candidate-schema.d.ts.map +1 -1
  165. package/out/candidate-schema.js +3 -0
  166. package/out/candidate-schema.js.map +1 -1
  167. package/out/candidate-scoring.d.ts +34 -0
  168. package/out/candidate-scoring.d.ts.map +1 -0
  169. package/out/candidate-scoring.js +196 -0
  170. package/out/candidate-scoring.js.map +1 -0
  171. package/out/capital-schema.d.ts +51 -0
  172. package/out/capital-schema.d.ts.map +1 -0
  173. package/out/capital-schema.js +63 -0
  174. package/out/capital-schema.js.map +1 -0
  175. package/out/capitals.d.ts +69 -0
  176. package/out/capitals.d.ts.map +1 -0
  177. package/out/capitals.js +98 -0
  178. package/out/capitals.js.map +1 -0
  179. package/out/coincident-roles.d.ts +12 -4
  180. package/out/coincident-roles.d.ts.map +1 -1
  181. package/out/coincident-roles.js +43 -11
  182. package/out/coincident-roles.js.map +1 -1
  183. package/out/{convention.d.ts → convention/index.d.ts} +5 -5
  184. package/out/convention/index.d.ts.map +1 -0
  185. package/out/{convention.js → convention/index.js} +4 -4
  186. package/out/convention/index.js.map +1 -0
  187. package/out/convention/schema.d.ts +51 -0
  188. package/out/convention/schema.d.ts.map +1 -0
  189. package/out/convention/schema.js +34 -0
  190. package/out/convention/schema.js.map +1 -0
  191. package/out/coverage-manifest-schema.d.ts +8 -8
  192. package/out/coverage-manifest-schema.d.ts.map +1 -1
  193. package/out/coverage-manifest-schema.js +6 -10
  194. package/out/coverage-manifest-schema.js.map +1 -1
  195. package/out/currency-backfill.d.ts +90 -0
  196. package/out/currency-backfill.d.ts.map +1 -0
  197. package/out/currency-backfill.js +196 -0
  198. package/out/currency-backfill.js.map +1 -0
  199. package/out/env.d.ts +43 -0
  200. package/out/env.d.ts.map +1 -0
  201. package/out/env.js +48 -0
  202. package/out/env.js.map +1 -0
  203. package/out/exact-match.d.ts +25 -0
  204. package/out/exact-match.d.ts.map +1 -0
  205. package/out/exact-match.js +88 -0
  206. package/out/exact-match.js.map +1 -0
  207. package/out/extracts.d.ts +114 -0
  208. package/out/extracts.d.ts.map +1 -0
  209. package/out/{sharding.js → extracts.js} +38 -38
  210. package/out/extracts.js.map +1 -0
  211. package/out/{fst-autocomplete.d.ts → fst/autocomplete.d.ts} +13 -13
  212. package/out/fst/autocomplete.d.ts.map +1 -0
  213. package/out/fst/autocomplete.js +113 -0
  214. package/out/fst/autocomplete.js.map +1 -0
  215. package/out/{fst-builder.d.ts → fst/builder.d.ts} +5 -5
  216. package/out/fst/builder.d.ts.map +1 -0
  217. package/out/{fst-builder.js → fst/builder.js} +24 -24
  218. package/out/fst/builder.js.map +1 -0
  219. package/out/{fst-deserialize-web.d.ts → fst/deserialize-web.d.ts} +3 -3
  220. package/out/fst/deserialize-web.d.ts.map +1 -0
  221. package/out/{fst-deserialize-web.js → fst/deserialize-web.js} +10 -74
  222. package/out/fst/deserialize-web.js.map +1 -0
  223. package/out/fst/format.d.ts +80 -0
  224. package/out/fst/format.d.ts.map +1 -0
  225. package/out/fst/format.js +91 -0
  226. package/out/fst/format.js.map +1 -0
  227. package/out/{fst-freshness.d.ts → fst/freshness.d.ts} +11 -14
  228. package/out/fst/freshness.d.ts.map +1 -0
  229. package/out/{fst-freshness.js → fst/freshness.js} +42 -74
  230. package/out/fst/freshness.js.map +1 -0
  231. package/out/fst/index.d.ts +14 -0
  232. package/out/fst/index.d.ts.map +1 -0
  233. package/out/fst/index.js +14 -0
  234. package/out/fst/index.js.map +1 -0
  235. package/out/{fst-matcher.d.ts → fst/matcher.d.ts} +2 -2
  236. package/out/fst/matcher.d.ts.map +1 -0
  237. package/out/{fst-matcher.js → fst/matcher.js} +1 -1
  238. package/out/fst/matcher.js.map +1 -0
  239. package/out/{fst-serialize.d.ts → fst/serialize.d.ts} +5 -10
  240. package/out/fst/serialize.d.ts.map +1 -0
  241. package/out/{fst-serialize.js → fst/serialize.js} +18 -98
  242. package/out/fst/serialize.js.map +1 -0
  243. package/out/{fst-types.d.ts → fst/types.d.ts} +4 -3
  244. package/out/fst/types.d.ts.map +1 -0
  245. package/out/{fst-types.js → fst/types.js} +1 -1
  246. package/out/fst/types.js.map +1 -0
  247. package/out/{fts.d.ts → fts/index.d.ts} +20 -13
  248. package/out/fts/index.d.ts.map +1 -0
  249. package/out/{fts.js → fts/index.js} +20 -15
  250. package/out/fts/index.js.map +1 -0
  251. package/out/{fts-query.d.ts → fts/query.d.ts} +2 -2
  252. package/out/fts/query.d.ts.map +1 -0
  253. package/out/{fts-query.js → fts/query.js} +2 -2
  254. package/out/fts/query.js.map +1 -0
  255. package/out/{geonames-aliases.d.ts → geonames/aliases.d.ts} +8 -6
  256. package/out/geonames/aliases.d.ts.map +1 -0
  257. package/out/{geonames-aliases.js → geonames/aliases.js} +7 -7
  258. package/out/geonames/aliases.js.map +1 -0
  259. package/out/geonames/index.d.ts +8 -0
  260. package/out/geonames/index.d.ts.map +1 -0
  261. package/out/geonames/index.js +8 -0
  262. package/out/geonames/index.js.map +1 -0
  263. package/out/{geonames-postal.d.ts → geonames/postal.d.ts} +12 -10
  264. package/out/geonames/postal.d.ts.map +1 -0
  265. package/out/{geonames-postal.js → geonames/postal.js} +11 -11
  266. package/out/geonames/postal.js.map +1 -0
  267. package/out/index.d.ts +32 -31
  268. package/out/index.d.ts.map +1 -1
  269. package/out/index.js +24 -24
  270. package/out/index.js.map +1 -1
  271. package/out/interpolation.d.ts +18 -9
  272. package/out/interpolation.d.ts.map +1 -1
  273. package/out/interpolation.js +108 -36
  274. package/out/interpolation.js.map +1 -1
  275. package/out/lookup.d.ts +25 -25
  276. package/out/lookup.d.ts.map +1 -1
  277. package/out/lookup.js +174 -558
  278. package/out/lookup.js.map +1 -1
  279. package/out/name-score.d.ts +0 -10
  280. package/out/name-score.d.ts.map +1 -1
  281. package/out/name-score.js +6 -4
  282. package/out/name-score.js.map +1 -1
  283. package/out/nsul/index.d.ts +8 -0
  284. package/out/nsul/index.d.ts.map +1 -0
  285. package/out/nsul/index.js +8 -0
  286. package/out/nsul/index.js.map +1 -0
  287. package/out/nsul/lookup.d.ts +83 -0
  288. package/out/nsul/lookup.d.ts.map +1 -0
  289. package/out/nsul/lookup.js +80 -0
  290. package/out/nsul/lookup.js.map +1 -0
  291. package/out/nsul/schema.d.ts +100 -0
  292. package/out/nsul/schema.d.ts.map +1 -0
  293. package/out/nsul/schema.js +78 -0
  294. package/out/nsul/schema.js.map +1 -0
  295. package/out/place-importance-schema.d.ts +47 -10
  296. package/out/place-importance-schema.d.ts.map +1 -1
  297. package/out/place-importance-schema.js +56 -10
  298. package/out/place-importance-schema.js.map +1 -1
  299. package/out/poi/index.d.ts +8 -0
  300. package/out/poi/index.d.ts.map +1 -0
  301. package/out/poi/index.js +8 -0
  302. package/out/poi/index.js.map +1 -0
  303. package/out/{poi-lookup.d.ts → poi/lookup.d.ts} +8 -10
  304. package/out/poi/lookup.d.ts.map +1 -0
  305. package/out/{poi-lookup.js → poi/lookup.js} +24 -29
  306. package/out/poi/lookup.js.map +1 -0
  307. package/out/{poi-schema.d.ts → poi/schema.d.ts} +10 -6
  308. package/out/poi/schema.d.ts.map +1 -0
  309. package/out/{poi-schema.js → poi/schema.js} +1 -1
  310. package/out/poi/schema.js.map +1 -0
  311. package/out/polygon-schema.d.ts +37 -0
  312. package/out/polygon-schema.d.ts.map +1 -0
  313. package/out/polygon-schema.js +23 -0
  314. package/out/polygon-schema.js.map +1 -0
  315. package/out/{postal-city-alias-lookup.d.ts → postal/city-alias-lookup.d.ts} +7 -6
  316. package/out/postal/city-alias-lookup.d.ts.map +1 -0
  317. package/out/{postal-city-alias-lookup.js → postal/city-alias-lookup.js} +12 -16
  318. package/out/postal/city-alias-lookup.js.map +1 -0
  319. package/out/{postal-city-alias-schema.d.ts → postal/city-alias-schema.d.ts} +1 -1
  320. package/out/{postal-city-alias-schema.d.ts.map → postal/city-alias-schema.d.ts.map} +1 -1
  321. package/out/{postal-city-alias-schema.js → postal/city-alias-schema.js} +1 -1
  322. package/out/{postal-city-alias-schema.js.map → postal/city-alias-schema.js.map} +1 -1
  323. package/out/{postal-city-candidate-schema.d.ts → postal/city-candidate-schema.d.ts} +4 -3
  324. package/out/postal/city-candidate-schema.d.ts.map +1 -0
  325. package/out/{postal-city-candidate-schema.js → postal/city-candidate-schema.js} +2 -2
  326. package/out/{postal-city-candidate-schema.js.map → postal/city-candidate-schema.js.map} +1 -1
  327. package/out/postal/index.d.ts +9 -0
  328. package/out/postal/index.d.ts.map +1 -0
  329. package/out/postal/index.js +9 -0
  330. package/out/postal/index.js.map +1 -0
  331. package/out/postcode-point-lookup.d.ts +10 -10
  332. package/out/postcode-point-lookup.d.ts.map +1 -1
  333. package/out/postcode-point-lookup.js +13 -13
  334. package/out/postcode-point-lookup.js.map +1 -1
  335. package/out/primary-preference.d.ts +133 -0
  336. package/out/primary-preference.d.ts.map +1 -0
  337. package/out/primary-preference.js +146 -0
  338. package/out/primary-preference.js.map +1 -0
  339. package/out/proximity-rerank.d.ts +77 -0
  340. package/out/proximity-rerank.d.ts.map +1 -0
  341. package/out/proximity-rerank.js +86 -0
  342. package/out/proximity-rerank.js.map +1 -0
  343. package/out/ranking-weights.d.ts +15 -3
  344. package/out/ranking-weights.d.ts.map +1 -1
  345. package/out/ranking-weights.js +21 -3
  346. package/out/ranking-weights.js.map +1 -1
  347. package/out/region-keys.d.ts +47 -0
  348. package/out/region-keys.d.ts.map +1 -0
  349. package/out/region-keys.js +121 -0
  350. package/out/region-keys.js.map +1 -0
  351. package/out/reverse.d.ts +6 -6
  352. package/out/reverse.d.ts.map +1 -1
  353. package/out/reverse.js +23 -35
  354. package/out/reverse.js.map +1 -1
  355. package/out/schema.d.ts +10 -1
  356. package/out/schema.d.ts.map +1 -1
  357. package/out/schema.js.map +1 -1
  358. package/out/search-fetch.d.ts +59 -0
  359. package/out/search-fetch.d.ts.map +1 -0
  360. package/out/search-fetch.js +184 -0
  361. package/out/search-fetch.js.map +1 -0
  362. package/out/sqlite-convention-source.d.ts +7 -7
  363. package/out/sqlite-convention-source.d.ts.map +1 -1
  364. package/out/sqlite-convention-source.js +5 -5
  365. package/out/sqlite-convention-source.js.map +1 -1
  366. package/out/sqlite-utils.d.ts +24 -6
  367. package/out/sqlite-utils.d.ts.map +1 -1
  368. package/out/sqlite-utils.js +24 -7
  369. package/out/sqlite-utils.js.map +1 -1
  370. package/out/{street-centroid-schema.d.ts → street/centroid-schema.d.ts} +12 -7
  371. package/out/street/centroid-schema.d.ts.map +1 -0
  372. package/out/{street-centroid-schema.js → street/centroid-schema.js} +5 -5
  373. package/out/{street-centroid-schema.js.map → street/centroid-schema.js.map} +1 -1
  374. package/out/{street-centroid.d.ts → street/centroid.d.ts} +6 -6
  375. package/out/street/centroid.d.ts.map +1 -0
  376. package/out/{street-centroid.js → street/centroid.js} +18 -24
  377. package/out/street/centroid.js.map +1 -0
  378. package/out/street/index.d.ts +13 -0
  379. package/out/street/index.d.ts.map +1 -0
  380. package/out/street/index.js +13 -0
  381. package/out/street/index.js.map +1 -0
  382. package/out/{street-morphology-fst-builder.d.ts → street/morphology-fst-builder.d.ts} +4 -4
  383. package/out/street/morphology-fst-builder.d.ts.map +1 -0
  384. package/out/{street-morphology-fst-builder.js → street/morphology-fst-builder.js} +18 -13
  385. package/out/street/morphology-fst-builder.js.map +1 -0
  386. package/out/{street-morphology-fst-loader.d.ts → street/morphology-fst-loader.d.ts} +5 -5
  387. package/out/street/morphology-fst-loader.d.ts.map +1 -0
  388. package/out/{street-morphology-fst-loader.js → street/morphology-fst-loader.js} +11 -10
  389. package/out/street/morphology-fst-loader.js.map +1 -0
  390. package/out/{street-name-lookup.d.ts → street/name-lookup.d.ts} +4 -7
  391. package/out/street/name-lookup.d.ts.map +1 -0
  392. package/out/{street-name-lookup.js → street/name-lookup.js} +9 -23
  393. package/out/street/name-lookup.js.map +1 -0
  394. package/out/street/normalize.d.ts +179 -0
  395. package/out/street/normalize.d.ts.map +1 -0
  396. package/out/street/normalize.js +457 -0
  397. package/out/street/normalize.js.map +1 -0
  398. package/out/{street-segment-schema.d.ts → street/segment-schema.d.ts} +13 -9
  399. package/out/street/segment-schema.d.ts.map +1 -0
  400. package/out/{street-segment-schema.js → street/segment-schema.js} +4 -4
  401. package/out/{street-segment-schema.js.map → street/segment-schema.js.map} +1 -1
  402. package/out/types.d.ts +49 -9
  403. package/out/types.d.ts.map +1 -1
  404. package/out/types.js.map +1 -1
  405. package/out/unified-schema.d.ts +6 -5
  406. package/out/unified-schema.d.ts.map +1 -1
  407. package/out/unified-schema.js +16 -19
  408. package/out/unified-schema.js.map +1 -1
  409. package/out/uprn/existence.d.ts +53 -0
  410. package/out/uprn/existence.d.ts.map +1 -0
  411. package/out/uprn/existence.js +61 -0
  412. package/out/uprn/existence.js.map +1 -0
  413. package/out/uprn/index.d.ts +9 -0
  414. package/out/uprn/index.d.ts.map +1 -0
  415. package/out/uprn/index.js +9 -0
  416. package/out/uprn/index.js.map +1 -0
  417. package/out/uprn/lookup.d.ts +85 -0
  418. package/out/uprn/lookup.d.ts.map +1 -0
  419. package/out/uprn/lookup.js +149 -0
  420. package/out/uprn/lookup.js.map +1 -0
  421. package/out/uprn/schema.d.ts +93 -0
  422. package/out/uprn/schema.d.ts.map +1 -0
  423. package/out/uprn/schema.js +78 -0
  424. package/out/uprn/schema.js.map +1 -0
  425. package/out/weights-overlay-linker.d.ts +299 -0
  426. package/out/weights-overlay-linker.d.ts.map +1 -0
  427. package/out/weights-overlay-linker.js +511 -0
  428. package/out/weights-overlay-linker.js.map +1 -0
  429. package/package.json +622 -154
  430. package/address-point.ts +0 -125
  431. package/candidate-lookup.ts +0 -720
  432. package/fst-autocomplete.ts +0 -204
  433. package/geo.ts +0 -121
  434. package/lookup.ts +0 -1354
  435. package/out/address-point-interpolation.d.ts.map +0 -1
  436. package/out/address-point-interpolation.js.map +0 -1
  437. package/out/address-point-schema.d.ts.map +0 -1
  438. package/out/address-point-schema.js.map +0 -1
  439. package/out/address-point.d.ts.map +0 -1
  440. package/out/address-point.js +0 -84
  441. package/out/address-point.js.map +0 -1
  442. package/out/ancestry-backfill.d.ts.map +0 -1
  443. package/out/ancestry-backfill.js.map +0 -1
  444. package/out/ancestry.d.ts.map +0 -1
  445. package/out/ancestry.js.map +0 -1
  446. package/out/convention.d.ts.map +0 -1
  447. package/out/convention.js.map +0 -1
  448. package/out/fst-autocomplete.d.ts.map +0 -1
  449. package/out/fst-autocomplete.js +0 -130
  450. package/out/fst-autocomplete.js.map +0 -1
  451. package/out/fst-builder.d.ts.map +0 -1
  452. package/out/fst-builder.js.map +0 -1
  453. package/out/fst-deserialize-web.d.ts.map +0 -1
  454. package/out/fst-deserialize-web.js.map +0 -1
  455. package/out/fst-freshness.d.ts.map +0 -1
  456. package/out/fst-freshness.js.map +0 -1
  457. package/out/fst-matcher.d.ts.map +0 -1
  458. package/out/fst-matcher.js.map +0 -1
  459. package/out/fst-serialize.d.ts.map +0 -1
  460. package/out/fst-serialize.js.map +0 -1
  461. package/out/fst-types.d.ts.map +0 -1
  462. package/out/fst-types.js.map +0 -1
  463. package/out/fts-query.d.ts.map +0 -1
  464. package/out/fts-query.js.map +0 -1
  465. package/out/fts.d.ts.map +0 -1
  466. package/out/fts.js.map +0 -1
  467. package/out/geo.d.ts +0 -74
  468. package/out/geo.d.ts.map +0 -1
  469. package/out/geo.js +0 -71
  470. package/out/geo.js.map +0 -1
  471. package/out/geonames-aliases.d.ts.map +0 -1
  472. package/out/geonames-aliases.js.map +0 -1
  473. package/out/geonames-postal.d.ts.map +0 -1
  474. package/out/geonames-postal.js.map +0 -1
  475. package/out/poi-lookup.d.ts.map +0 -1
  476. package/out/poi-lookup.js.map +0 -1
  477. package/out/poi-schema.d.ts.map +0 -1
  478. package/out/poi-schema.js.map +0 -1
  479. package/out/postal-city-alias-lookup.d.ts.map +0 -1
  480. package/out/postal-city-alias-lookup.js.map +0 -1
  481. package/out/postal-city-candidate-schema.d.ts.map +0 -1
  482. package/out/sharding.d.ts +0 -114
  483. package/out/sharding.d.ts.map +0 -1
  484. package/out/sharding.js.map +0 -1
  485. package/out/street-centroid-schema.d.ts.map +0 -1
  486. package/out/street-centroid.d.ts.map +0 -1
  487. package/out/street-centroid.js.map +0 -1
  488. package/out/street-morphology-fst-builder.d.ts.map +0 -1
  489. package/out/street-morphology-fst-builder.js.map +0 -1
  490. package/out/street-morphology-fst-loader.d.ts.map +0 -1
  491. package/out/street-morphology-fst-loader.js.map +0 -1
  492. package/out/street-name-lookup.d.ts.map +0 -1
  493. package/out/street-name-lookup.js.map +0 -1
  494. package/out/street-normalize.d.ts +0 -95
  495. package/out/street-normalize.d.ts.map +0 -1
  496. package/out/street-normalize.js +0 -272
  497. package/out/street-normalize.js.map +0 -1
  498. package/out/street-segment-schema.d.ts.map +0 -1
  499. package/sqlite-utils.ts +0 -46
  500. package/street-normalize.ts +0 -318
  501. /package/{postal-city-alias-schema.ts → lib/postal/city-alias-schema.ts} +0 -0
package/lib/env.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Environment variables naming the WOF SQLite artifacts this resolver opens, and the development weights the overlay
7
+ * linker materializes. CLI flags (`--resolve-db`, …) override these at the call site.
8
+ */
9
+
10
+ import { $public as corePublic, liveEnv } from "@mailwoman/core/env"
11
+ import { z } from "zod"
12
+
13
+ /**
14
+ * Gazetteer and resolver database paths, plus the development weights overlay.
15
+ */
16
+ export const PublicWOFSQLiteEnvSchema = z.object({
17
+ MAILWOMAN_WOF_DB: z.string().optional().meta({
18
+ title: "Who's On First database",
19
+ description: "Path to a full Who's On First SQLite distribution used by the search resolver backend.",
20
+ }),
21
+ MAILWOMAN_WOF_ADMIN_DB: z.string().optional().meta({
22
+ title: "Who's On First admin database",
23
+ description: "Path to the full Who's On First distribution used for reverse geocoding.",
24
+ }),
25
+ MAILWOMAN_WOF_POLYGONS_DB: z.string().optional().meta({
26
+ title: "Who's On First polygon database",
27
+ description: "Path to the polygon sidecar used for exact reverse-geocode containment.",
28
+ }),
29
+ MAILWOMAN_CANDIDATE_DB: z.string().optional().meta({
30
+ title: "Candidate database",
31
+ description: "Path to candidate.db; set to `none` to pin the full-distribution search backend.",
32
+ }),
33
+ MAILWOMAN_POSTAL_CITY_ALIAS_DB: z.string().optional().meta({
34
+ title: "Postal city alias database",
35
+ description: "Path to postal-city alias data used for alias-aware resolution of U.S. mailing addresses.",
36
+ }),
37
+ MAILWOMAN_DEV_MODEL: z.string().optional().meta({
38
+ title: "Development model",
39
+ description: "Path to the model artifact linked for local neural-weights development.",
40
+ }),
41
+ MAILWOMAN_DEV_TOKENIZER: z.string().optional().meta({
42
+ title: "Development tokenizer",
43
+ description: "Path to the tokenizer artifact linked for local neural-weights development.",
44
+ }),
45
+ })
46
+
47
+ /**
48
+ * Live resolver settings over core's, sharing core's getters and cached values.
49
+ */
50
+ export const $public = liveEnv(PublicWOFSQLiteEnvSchema, corePublic)
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The exact-match tier's name probes — the two case-folded equality lookups that decide whether a
7
+ * candidate holds the query text as its own name, an alias, or an official name.
8
+ */
9
+
10
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
11
+
12
+ import { aliasBagExactMatch, foldQueryText } from "#fts/index"
13
+
14
+ /**
15
+ * Among `ids`, return the subset whose name OR any alias equals `text` case-insensitively — the exact-match tier for
16
+ * ranking. One indexed query over `<schema>.names`. When the extract has no `names` table (a slim DB built with
17
+ * `dropNames`, or a postcode-only extract), fall back to the self-contained `place_search` FTS content: its `alt_names`
18
+ * column is the same alias set joined on the boundary-preserving `ALIAS_SEPARATOR` (#523), so `aliasBagExactMatch`
19
+ * recovers the exact alias tier ("New York City" → New York) that the dropped `names` table used to provide.
20
+ */
21
+ export function exactMatchIDs<DB>(
22
+ db: DatabaseClient<DB>,
23
+ schemaName: string,
24
+ ids: number[],
25
+ text: string
26
+ ): Set<number> {
27
+ const out = new Set<number>()
28
+ const trimmed = text.trim()
29
+
30
+ if (!ids.length || !trimmed) return out
31
+ const placeholders = ids.map(() => "?").join(", ")
32
+
33
+ try {
34
+ const rows = db
35
+ .prepare(`SELECT DISTINCT id FROM ${schemaName}.names WHERE id IN (${placeholders}) AND name = ? COLLATE NOCASE`)
36
+ .all(...ids, trimmed) as Array<{ id: number }>
37
+
38
+ for (const r of rows) {
39
+ out.add(r.id)
40
+ }
41
+
42
+ return out
43
+ } catch {
44
+ // No `names` table on this extract — fall through to the place_search alias bag.
45
+ }
46
+
47
+ try {
48
+ const rows = db
49
+ .prepare(`SELECT wof_id AS id, name, alt_names FROM ${schemaName}.place_search WHERE wof_id IN (${placeholders})`)
50
+ .all(...ids) as Array<{ id: number; name: string | null; alt_names: string | null }>
51
+
52
+ const needle = foldQueryText(trimmed)
53
+
54
+ for (const r of rows) {
55
+ if (r.name !== null && foldQueryText(r.name) === needle) {
56
+ out.add(r.id)
57
+ }
58
+ }
59
+
60
+ // Alias pass via the shared bag parser (#523). Separated bags (built since #523) get a true
61
+ // per-alias equality check, unrestricted — matching the `names`-table branch above, where an
62
+ // alias match counts as exact regardless of other candidates. Legacy bags (no separator)
63
+ // fall back to padded containment, conditioned on "no canonical exact in the pool" because their
64
+ // lost boundaries would otherwise false-promote interior fragments ("York" inside the alias
65
+ // "New York City") or cross-alias fragments ("York New" across "…York" + "New City…").
66
+ const anyCanonicalExact = out.size > 0
67
+
68
+ for (const r of rows) {
69
+ if (aliasBagExactMatch(r.alt_names, needle, anyCanonicalExact)) {
70
+ out.add(r.id)
71
+ }
72
+ }
73
+ } catch {
74
+ // Extract without place_search either → no exact-match tier. Falls back to weighted-sum order.
75
+ }
76
+
77
+ return out
78
+ }
79
+
80
+ /**
81
+ * Among `ids` (already known exact matches), the subset holding `text` as an OFFICIAL name (`names.official = 1`, the
82
+ * #940 ingest bit). Same COLLATE NOCASE semantics as {@link WOFSQLitePlaceLookup.#exactMatchIDs} so the two probes
83
+ * agree on what "equals the query" means. Fails soft on gazetteers built before #940 (no `official` column) — the
84
+ * sub-tier then behaves exactly as if `officialNameExact` were off.
85
+ */
86
+ export function officialNameIDs<DB>(
87
+ db: DatabaseClient<DB>,
88
+ schemaName: string,
89
+ ids: number[],
90
+ text: string
91
+ ): Set<number> {
92
+ const out = new Set<number>()
93
+ const trimmed = text.trim()
94
+
95
+ if (!ids.length || !trimmed) return out
96
+ const placeholders = ids.map(() => "?").join(", ")
97
+
98
+ try {
99
+ const rows = db
100
+ .prepare(
101
+ `SELECT DISTINCT id FROM ${schemaName}.names WHERE id IN (${placeholders}) AND official = 1 AND name = ? COLLATE NOCASE`
102
+ )
103
+ .all(...ids, trimmed) as Array<{ id: number }>
104
+
105
+ for (const r of rows) {
106
+ out.add(r.id)
107
+ }
108
+ } catch {
109
+ // Pre-#940 gazetteer (no `official` column) or a names-less slim extract — feature inert.
110
+ }
111
+
112
+ return out
113
+ }
@@ -1,16 +1,18 @@
1
+ import { basename } from "path-ts"
2
+
1
3
  /**
2
4
  * @copyright Sister Software
3
5
  * @license AGPL-3.0
4
6
  * @author Teffen Ellis, et al.
5
7
  *
6
- * Multi-shard support for `WOFSqlitePlaceLookup` — opens multiple WOF SQLite distributions on one
7
- * connection via `ATTACH DATABASE`, and routes queries to the right shard based on placetype.
8
+ * Multi-extract support for `WOFSQLitePlaceLookup` — opens multiple WOF SQLite distributions on one
9
+ * connection via `ATTACH DATABASE`, and routes queries to the right extract based on placetype.
8
10
  *
9
11
  * ## The FTS5 syntax rule that drove this design
10
12
  *
11
13
  * The naive `SELECT … FROM pc.place_search WHERE pc.place_search MATCH ?` fails — SQLite parses the
12
14
  * schema-qualified table on the left of MATCH as "column place_search of table pc". Discovered in
13
- * the spike at PR review time; documented as `_SHARD_RULE.md` should it ever bite again.
15
+ * the spike at PR review time; documented as `_EXTRACT_RULE.md` should it ever bite again.
14
16
  *
15
17
  * The working form: schema-qualified in FROM, bare table name in MATCH:
16
18
  *
@@ -18,15 +20,13 @@
18
20
  * SELECT … FROM pc.place_search WHERE place_search MATCH ?
19
21
  * ```
20
22
  *
21
- * Identical table names across attached shards (which is what we have — every shard ships its own
23
+ * Identical table names across attached extracts (which is what we have — every extract ships its own
22
24
  * `place_search` + `place_bbox`) are fine because the bare-name MATCH resolves against FROM
23
25
  * scope.
24
26
  */
25
27
 
26
- import { basename } from "node:path"
27
-
28
28
  /**
29
- * Derive a SQL-safe schema name from a WOF distribution filename. Used by `ATTACH DATABASE … AS <name>` so each shard
29
+ * Derive a SQL-safe schema name from a WOF distribution filename. Used by `ATTACH DATABASE … AS <name>` so each extract
30
30
  * gets a stable, predictable handle.
31
31
  *
32
32
  * Convention strips the `whosonfirst-data-` prefix and the `-latest.db` (or just `.db`) suffix, then replaces `-` with
@@ -39,7 +39,7 @@ import { basename } from "node:path"
39
39
  * - `whosonfirst-data-admin-latest.db` → `admin`
40
40
  * - `my-custom.db` → `my_custom`
41
41
  *
42
- * Callers can override the derived name explicitly via `ShardConfig.schemaName` when the filename doesn't follow WOF
42
+ * Callers can override the derived name explicitly via `ExtractConfig.schemaName` when the filename doesn't follow WOF
43
43
  * convention.
44
44
  */
45
45
  export function deriveSchemaName(path: string): string {
@@ -57,11 +57,11 @@ export function deriveSchemaName(path: string): string {
57
57
  }
58
58
 
59
59
  /**
60
- * Per-shard configuration. The simple form is just a path string — the schema name is derived from it. The object form
61
- * lets callers override the derived schema name (useful when a filename doesn't follow WOF convention) or attach an
62
- * extra hint about which placetypes route here.
60
+ * Per-extract configuration. The simple form is just a path string — the schema name is derived from it. The object
61
+ * form lets callers override the derived schema name (useful when a filename doesn't follow WOF convention) or attach
62
+ * an extra hint about which placetypes route here.
63
63
  */
64
- export interface ShardConfig {
64
+ export interface ExtractConfig {
65
65
  path: string
66
66
  /**
67
67
  * Override the auto-derived schema name. Useful when the filename doesn't match WOF convention or when you want a
@@ -69,18 +69,18 @@ export interface ShardConfig {
69
69
  */
70
70
  schemaName?: string
71
71
  /**
72
- * Optional explicit list of placetypes this shard serves. When set, queries against any listed placetype are routed
73
- * to this shard. When omitted, routing falls back to a name-match heuristic: a shard whose `schemaName` contains the
74
- * placetype as a substring (e.g. `postalcode_us` for `postalcode` queries) is preferred for that placetype.
72
+ * Optional explicit list of placetypes this extract serves. When set, queries against any listed placetype are routed
73
+ * to this extract. When omitted, routing falls back to a name-match heuristic: a extract whose `schemaName` contains
74
+ * the placetype as a substring (e.g. `postalcode_us` for `postalcode` queries) is preferred for that placetype.
75
75
  */
76
76
  placetypes?: readonly string[]
77
77
  }
78
78
 
79
79
  /**
80
80
  * Resolved post-derivation: paired path + chosen schema name + (possibly empty) placetypes hint. Used internally by
81
- * `WOFSqlitePlaceLookup` so the routing logic operates on uniform structures.
81
+ * `WOFSQLitePlaceLookup` so the routing logic operates on uniform structures.
82
82
  */
83
- export interface ResolvedShard {
83
+ export interface ResolvedExtract {
84
84
  path: string
85
85
  schemaName: string
86
86
  placetypes: readonly string[]
@@ -93,40 +93,40 @@ const SQLITE_IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/u
93
93
 
94
94
  /**
95
95
  * Normalize the user-provided `databasePath` opt (which may be a single string, an array of strings, or an array of
96
- * `ShardConfig` objects) into a uniform `ResolvedShard[]`.
96
+ * `ExtractConfig` objects) into a uniform `ResolvedExtract[]`.
97
97
  *
98
- * The first shard becomes `main` regardless of its derived schema name — that's the SQLite convention. Subsequent
99
- * shards keep their derived (or override) schema name.
98
+ * The first extract becomes `main` regardless of its derived schema name — that's the SQLite convention. Subsequent
99
+ * extracts keep their derived (or override) schema name.
100
100
  */
101
- export function resolveShards(input: string | ReadonlyArray<string | ShardConfig>): ResolvedShard[] {
101
+ export function resolveExtracts(input: string | ReadonlyArray<string | ExtractConfig>): ResolvedExtract[] {
102
102
  const list = typeof input === "string" ? [input] : input
103
103
 
104
- if (!list.length) throw new Error("resolveShards: at least one shard is required")
104
+ if (!list.length) throw new Error("resolveExtracts: at least one extract is required")
105
105
 
106
106
  const seen = new Set<string>()
107
- const out: ResolvedShard[] = []
107
+ const out: ResolvedExtract[] = []
108
108
 
109
109
  for (let i = 0; i < list.length; i++) {
110
110
  const entry = list[i]!
111
- const cfg: ShardConfig = typeof entry === "string" ? { path: entry } : entry
111
+ const cfg: ExtractConfig = typeof entry === "string" ? { path: entry } : entry
112
112
  const derived = cfg.schemaName ?? deriveSchemaName(cfg.path)
113
113
 
114
114
  if (!SQLITE_IDENT_RE.test(derived)) {
115
115
  throw new Error(
116
- `resolveShards: schema name ${JSON.stringify(derived)} is not a valid SQLite identifier ` +
116
+ `resolveExtracts: schema name ${JSON.stringify(derived)} is not a valid SQLite identifier ` +
117
117
  `(derived from path ${JSON.stringify(cfg.path)}). Pass an explicit ` +
118
118
  `{ path, schemaName } to override.`
119
119
  )
120
120
  }
121
121
 
122
- // The first shard is always main per SQLite semantics — its derived name is informational
123
- // only. Subsequent shards must have unique non-main names.
122
+ // The first extract is always main per SQLite semantics — its derived name is informational
123
+ // only. Subsequent extracts must have unique non-main names.
124
124
  const schemaName = i === 0 ? "main" : derived
125
125
 
126
126
  if (i > 0 && (schemaName === "main" || seen.has(schemaName))) {
127
127
  throw new Error(
128
- `resolveShards: schema name ${JSON.stringify(schemaName)} collides ` +
129
- `(either with "main" or another shard). Pass an explicit { path, schemaName }.`
128
+ `resolveExtracts: schema name ${JSON.stringify(schemaName)} collides ` +
129
+ `(either with "main" or another extract). Pass an explicit { path, schemaName }.`
130
130
  )
131
131
  }
132
132
 
@@ -143,36 +143,39 @@ export function resolveShards(input: string | ReadonlyArray<string | ShardConfig
143
143
  }
144
144
 
145
145
  /**
146
- * Pick the shard to route a query to given the requested placetype(s).
146
+ * Pick the extract to route a query to given the requested placetype(s).
147
147
  *
148
148
  * Routing rules, in order:
149
149
  *
150
- * 1. If any shard has explicit `placetypes` that includes the requested placetype, use it.
151
- * 2. Otherwise, if a non-main shard's `schemaName` matches the placetype (e.g. `postalcode_us` matches `postalcode`), use
152
- * it.
150
+ * 1. If any extract has explicit `placetypes` that includes the requested placetype, use it.
151
+ * 2. Otherwise, if a non-main extract's `schemaName` matches the placetype (e.g. `postalcode_us` matches `postalcode`),
152
+ * use it.
153
153
  * 3. Otherwise, fall back to `main`.
154
154
  *
155
- * This deliberately doesn't UNION across shards — BM25 scores aren't comparable across separately- indexed corpora, and
156
- * the typical mailwoman query has a single placetype anyway. If a caller needs cross-shard results they can issue two
157
- * `findPlace` calls.
155
+ * This deliberately doesn't UNION across extracts — BM25 scores aren't comparable across separately- indexed corpora,
156
+ * and the typical mailwoman query has a single placetype anyway. If a caller needs cross-extract results they can issue
157
+ * two `findPlace` calls.
158
158
  */
159
159
  /**
160
- * All placetype-matching shards, in routing order (the country-aware pick chooses among these). Used by the bias path:
161
- * a country-less postcode query with proximity hints fans out across every matching shard and merges, because
162
- * single-shard routing would hide the cross-country ambiguity the hints exist to resolve ("48026" lives in
160
+ * All placetype-matching extracts, in routing order (the country-aware pick chooses among these). Used by the bias
161
+ * path: a country-less postcode query with proximity hints fans out across every matching extract and merges, because
162
+ * single-extract routing would hide the cross-country ambiguity the hints exist to resolve ("48026" lives in
163
163
  * postalcode-us AND postalcode-intl).
164
164
  */
165
- export function pickShardsForPlacetype(shards: ResolvedShard[], placetype: string | undefined): ResolvedShard[] {
166
- if (!placetype) return [shards[0]!]
167
- const matches: ResolvedShard[] = []
168
-
169
- for (const s of shards) {
165
+ export function pickExtractsForPlacetype(
166
+ extracts: ResolvedExtract[],
167
+ placetype: string | undefined
168
+ ): ResolvedExtract[] {
169
+ if (!placetype) return [extracts[0]!]
170
+ const matches: ResolvedExtract[] = []
171
+
172
+ for (const s of extracts) {
170
173
  if (s.placetypes.includes(placetype)) {
171
174
  matches.push(s)
172
175
  }
173
176
  }
174
177
 
175
- for (const s of shards) {
178
+ for (const s of extracts) {
176
179
  if (s.schemaName === "main" || matches.includes(s)) continue
177
180
 
178
181
  if (
@@ -184,38 +187,38 @@ export function pickShardsForPlacetype(shards: ResolvedShard[], placetype: strin
184
187
  }
185
188
  }
186
189
 
187
- return matches.length ? matches : [shards[0]!]
190
+ return matches.length ? matches : [extracts[0]!]
188
191
  }
189
192
 
190
- export function pickShardForPlacetype(
191
- shards: ResolvedShard[],
193
+ export function pickExtractForPlacetype(
194
+ extracts: ResolvedExtract[],
192
195
  placetype: string | undefined,
193
196
  opts?: {
194
197
  /**
195
- * #920: the query's country constraint, when the caller has one. With MULTIPLE shards matching a placetype
196
- * (postalcode-us + postalcode-geonames-tail), first-match routing sent every postcode query to the first shard and
197
- * starved the rest — a FI postcode could never reach the tail shard. When `country` is given and a matching shard's
198
- * probed country set contains it, that shard wins; shards without the country are skipped; the placetype-match
199
- * order remains the tiebreak when no shard claims the country (or none was probed).
198
+ * #920: the query's country constraint, when the caller has one. With MULTIPLE extracts matching a placetype
199
+ * (postalcode-us + postalcode-geonames-tail), first-match routing sent every postcode query to the first extract
200
+ * and starved the rest — a FI postcode could never reach the tail extract. When `country` is given and a matching
201
+ * extract's probed country set contains it, that extract wins; extracts without the country are skipped; the
202
+ * placetype-match order remains the tiebreak when no extract claims the country (or none was probed).
200
203
  */
201
204
  country?: string
202
205
  /**
203
- * Per-schema probed country sets (see `WOFSqlitePlaceLookup`'s construction probe).
206
+ * Per-schema probed country sets (see `WOFSQLitePlaceLookup`'s construction probe).
204
207
  */
205
208
  countriesBySchema?: ReadonlyMap<string, ReadonlySet<string>>
206
209
  }
207
- ): ResolvedShard {
208
- if (!placetype) return shards[0]!
210
+ ): ResolvedExtract {
211
+ if (!placetype) return extracts[0]!
209
212
 
210
- const matches: ResolvedShard[] = []
213
+ const matches: ResolvedExtract[] = []
211
214
 
212
- for (const s of shards) {
215
+ for (const s of extracts) {
213
216
  if (s.placetypes.includes(placetype)) {
214
217
  matches.push(s)
215
218
  }
216
219
  }
217
220
 
218
- for (const s of shards) {
221
+ for (const s of extracts) {
219
222
  if (s.schemaName === "main" || matches.includes(s)) continue
220
223
 
221
224
  // Substring match: `postalcode_us` matches `postalcode`. Conservative — requires the
@@ -230,7 +233,7 @@ export function pickShardForPlacetype(
230
233
  }
231
234
  }
232
235
 
233
- if (!matches.length) return shards[0]!
236
+ if (!matches.length) return extracts[0]!
234
237
 
235
238
  if (opts?.country && opts.countriesBySchema) {
236
239
  for (const s of matches) {
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * FST-based autocomplete — mailwoman vocabulary over `@mailwoman/ancestrie`'s generic algorithm
7
+ * (#1728 phase 2). The #587 behavior — prefix walk + BFS expansion, partial-last-token completion,
8
+ * per-branch capping, dedupe — lives in ancestrie's `autocomplete`; this module contributes only
9
+ * the storage adapter ({@link FSTMatcher} → `AncestrieReaderLike`) and the mapping back to
10
+ * mailwoman's suggestion shape (name, placetype, referential/encyclopedic, WOF ids).
11
+ *
12
+ * THE BYTES DO NOT MIGRATE. The shipped artifacts are `FST\0` v1–v5 (`fst-serialize.ts`), not
13
+ * ancestrie's `ANCT`: ancestrie entries are id-keyed with one record per id, while an FST place row
14
+ * is per-(surface, place) — `crossCountryBranches` is a property of the SURFACE, so the same wofID
15
+ * legitimately carries different values under different aliases and cannot be represented id-keyed.
16
+ * The matcher, both deserializers, and the serializer therefore stay here; what migrated is the
17
+ * ALGORITHM, which is the half that drifts (the #861 share-the-function rule).
18
+ */
19
+
20
+ import type {
21
+ AncestrieContinuation,
22
+ AncestrieMatch,
23
+ AncestrieReaderLike,
24
+ AncestrieRecord,
25
+ AncestrieSuggestion,
26
+ } from "@mailwoman/ancestrie"
27
+ import { autocomplete as ancestrieAutocomplete } from "@mailwoman/ancestrie"
28
+
29
+ import type { FSTMatcher } from "#fst/matcher"
30
+ import { normalizeTokens } from "#fst/matcher"
31
+ import type { PlaceEntry } from "#fst/types"
32
+
33
+ export interface AutocompleteResult {
34
+ query: string
35
+ normalizedTokens: string[]
36
+ depth: number
37
+ suggestions: AutocompleteSuggestion[]
38
+ }
39
+
40
+ export interface AutocompleteSuggestion {
41
+ name: string
42
+ placetype: string
43
+ /**
44
+ * The REFERENTIAL likelihood the suggestion is ranked by (ROAD_TO_V9 §2). Autocomplete answers "which place does the
45
+ * user mean", so it ranks referentially like everything else; encyclopedic importance rides along on
46
+ * {@link AutocompleteSuggestion.encyclopedic} for display and never enters the order.
47
+ */
48
+ referential: number
49
+ /**
50
+ * Encyclopedic (Wikipedia) importance, when the FST artifact carries one for this place. `undefined` = no article, or
51
+ * a pre-v5 binary — never 0.
52
+ */
53
+ encyclopedic?: number
54
+ wofID: number
55
+ parentChain: number[]
56
+ matchDepth: number
57
+ completionTokens: string[]
58
+ }
59
+
60
+ export interface AutocompleteOpts {
61
+ maxSuggestions?: number
62
+ maxExpansionDepth?: number
63
+ /**
64
+ * Collapse same-name suggestions to the single highest-referential one. Off by default (the CLI surfaces distinct
65
+ * same-name places — New York the city vs the county); a typeahead wants it ON so the dropdown isn't four "New
66
+ * London"s. (#587)
67
+ */
68
+ dedupeByName?: boolean
69
+ }
70
+
71
+ /**
72
+ * Max accepting entries collected per BFS branch — keeps one dense branch from starving the search.
73
+ */
74
+ const PER_BRANCH = 4
75
+
76
+ /**
77
+ * The top-`k` entries by REFERENTIAL likelihood (descending). Avoids sorting/allocating when `entries` is small — and
78
+ * that shortcut is part of the observable contract: at or under `k` the INSERTION order is served, which decides
79
+ * suggestion order among referential ties.
80
+ */
81
+ function topByReferential(entries: readonly PlaceEntry[], k: number): PlaceEntry[] {
82
+ if (entries.length <= k) return [...entries]
83
+
84
+ return [...entries].toSorted((a, b) => b.referential - a.referential).slice(0, k)
85
+ }
86
+
87
+ /**
88
+ * {@link FSTMatcher} presented through ancestrie's storage interface. Records carry the {@link PlaceEntry} itself as the
89
+ * payload, so the entry that WINS the algorithm's shallowest-depth rule is the entry whose fields the suggestion
90
+ * reports — a side lookup keyed on id could pick a different surface's row (`crossCountryBranches` differs per
91
+ * surface).
92
+ */
93
+ class FSTReader implements AncestrieReaderLike<PlaceEntry> {
94
+ readonly #fst: FSTMatcher
95
+
96
+ /**
97
+ * Parent chains of the entries this reader has served, id-keyed. A place's chain is identical across its surfaces (it
98
+ * is place-row data), so last-write-wins is safe. The algorithm asks {@link FSTReader.ancestorsOf} only for ids it
99
+ * just received from {@link FSTReader.entriesAt}, so serving from this memo answers every real call without an
100
+ * artifact-wide id index.
101
+ */
102
+ readonly #chains = new Map<number, number[]>()
103
+
104
+ constructor(fst: FSTMatcher) {
105
+ this.#fst = fst
106
+ }
107
+
108
+ walk(tokens: readonly string[]): AncestrieMatch | null {
109
+ return this.#fst.walk([...tokens])
110
+ }
111
+
112
+ continuations(stateID: number): AncestrieContinuation[] {
113
+ // Insertion order, verbatim — BFS visit order under the suggestion budget depends on it.
114
+ return this.#fst.continuations(stateID).map((c) => ({
115
+ token: c.token,
116
+ targetState: c.targetState,
117
+ entryCount: c.acceptingCount,
118
+ }))
119
+ }
120
+
121
+ entriesAt(stateID: number, limit?: number): AncestrieRecord<PlaceEntry>[] {
122
+ const places = this.#fst.accepting(stateID)
123
+ const selected = limit === undefined ? places : topByReferential(places, limit)
124
+
125
+ return selected.map((entry) => {
126
+ this.#chains.set(entry.wofID, entry.parentChain)
127
+
128
+ return {
129
+ id: entry.wofID,
130
+ rank: entry.referential,
131
+ parentIDs: entry.parentChain,
132
+ payload: entry,
133
+ }
134
+ })
135
+ }
136
+
137
+ ancestorsOf(id: number): number[] {
138
+ return this.#chains.get(id) ?? []
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Autocomplete from the current prefix. Returns suggestions ranked referential-descending.
144
+ */
145
+ export function autocomplete(fst: FSTMatcher, query: string, opts: AutocompleteOpts = {}): AutocompleteResult {
146
+ const normalizedTokens = normalizeTokens(query)
147
+
148
+ const result = ancestrieAutocomplete<PlaceEntry>(new FSTReader(fst), normalizedTokens, {
149
+ ...(opts.maxSuggestions === undefined ? {} : { maxSuggestions: opts.maxSuggestions }),
150
+ ...(opts.maxExpansionDepth === undefined ? {} : { maxExpansionDepth: opts.maxExpansionDepth }),
151
+ perBranchLimit: PER_BRANCH,
152
+ // The dedupe key is the DISPLAY name, not the token path: two surfaces of one name must still collapse. (#587)
153
+ ...(opts.dedupeByName ? { dedupe: (s: AncestrieSuggestion<PlaceEntry>) => s.payload!.name.toLowerCase() } : {}),
154
+ })
155
+
156
+ return {
157
+ query,
158
+ normalizedTokens,
159
+ depth: result.depth,
160
+ suggestions: result.suggestions.map((s) => {
161
+ // Every record this adapter serves carries its entry; the assertion documents the invariant.
162
+ const entry = s.payload!
163
+
164
+ return {
165
+ name: entry.name,
166
+ placetype: entry.placetype,
167
+ referential: entry.referential,
168
+ ...(entry.encyclopedic === undefined ? {} : { encyclopedic: entry.encyclopedic }),
169
+ wofID: s.id,
170
+ parentChain: s.parentIDs,
171
+ matchDepth: s.matchDepth,
172
+ completionTokens: s.completionTokens,
173
+ }
174
+ }),
175
+ }
176
+ }