@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.
- package/README.md +9 -8
- package/lib/address/point/interpolation.ts +45 -39
- package/lib/address/point/schema.ts +17 -40
- package/lib/address/point.ts +316 -0
- package/lib/{address/index.ts → address.ts} +1 -1
- package/lib/ancestry/backfill.ts +20 -86
- package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
- package/lib/build/candidate.ts +122 -174
- package/lib/build/population-corrections.ts +64 -0
- package/lib/build/slim.ts +44 -114
- package/lib/candidate/alias-bags.ts +16 -17
- package/lib/candidate/ancestors/schema.ts +77 -61
- package/lib/candidate/ancestors/sidecar.ts +15 -22
- package/lib/candidate/country-display-names.ts +6 -20
- package/lib/candidate/extract-fold.ts +10 -55
- package/lib/candidate/fts.ts +5 -23
- package/lib/candidate/importance.ts +65 -68
- package/lib/candidate/lookup.ts +124 -415
- package/lib/candidate/name-roles.ts +15 -33
- package/lib/candidate/own-name.ts +35 -32
- package/lib/candidate/place-attrs.ts +20 -13
- package/lib/candidate/schema.ts +29 -65
- package/lib/candidate/scoring.ts +54 -93
- package/lib/capital-schema.ts +21 -17
- package/lib/capitals.ts +29 -27
- package/lib/coincident-roles.ts +85 -55
- package/lib/convention/schema.ts +15 -16
- package/lib/convention.ts +144 -0
- package/lib/coverage-manifest-schema.ts +40 -45
- package/lib/currency-backfill.ts +34 -79
- package/lib/env.ts +1 -1
- package/lib/exact-match.ts +32 -21
- package/lib/extracts.ts +97 -74
- package/lib/fst/autocomplete.ts +54 -37
- package/lib/fst/builder.ts +37 -41
- package/lib/fst/deserialize-web.ts +16 -17
- package/lib/fst/format.ts +41 -23
- package/lib/fst/freshness.ts +43 -120
- package/lib/fst/matcher.ts +7 -5
- package/lib/fst/serialize.ts +54 -74
- package/lib/fst/types.ts +78 -75
- package/lib/{fst/index.ts → fst.ts} +1 -0
- package/lib/fts/query.ts +5 -36
- package/lib/fts.ts +280 -0
- package/lib/geonames/aliases.ts +73 -144
- package/lib/geonames/postal.ts +162 -71
- package/lib/index.ts +9 -8
- package/lib/interpolation.ts +48 -100
- package/lib/lookup.ts +155 -270
- package/lib/name-score.ts +9 -7
- package/lib/nsul/lookup.ts +37 -54
- package/lib/nsul/schema.ts +16 -43
- package/lib/paths.ts +159 -0
- package/lib/place-importance-schema.ts +62 -169
- package/lib/poi/lookup.ts +104 -121
- package/lib/poi/schema.ts +42 -26
- package/lib/polygon-schema.ts +10 -6
- package/lib/postal/city/alias/lookup.ts +22 -40
- package/lib/postal/city/alias/schema.ts +14 -10
- package/lib/postal/city/candidate-schema.ts +26 -20
- package/lib/postcode-point-lookup.ts +8 -27
- package/lib/primary-preference.ts +46 -126
- package/lib/proximity-rerank.ts +44 -33
- package/lib/ranking-weights.ts +105 -78
- package/lib/region-keys.ts +70 -42
- package/lib/reverse.ts +149 -86
- package/lib/schema.ts +73 -50
- package/lib/search-fetch.ts +90 -68
- package/lib/sqlite/convention-source.ts +15 -23
- package/lib/sqlite/utils.ts +19 -22
- package/lib/street/centroid/schema.ts +37 -42
- package/lib/street/{centroid/index.ts → centroid.ts} +16 -25
- package/lib/street/morphology/fst/builder.ts +60 -42
- package/lib/street/morphology/fst/loader.ts +40 -32
- package/lib/street/name-lookup.ts +45 -41
- package/lib/street/normalize.ts +88 -219
- package/lib/street/segment-schema.ts +116 -43
- package/lib/{street/index.ts → street.ts} +1 -1
- package/lib/types.ts +109 -150
- package/lib/unified-schema.ts +29 -29
- package/lib/uprn/existence.ts +30 -19
- package/lib/uprn/lookup.ts +29 -68
- package/lib/uprn/schema.ts +18 -40
- package/lib/weights-overlay-linker.ts +236 -252
- package/out/address/point/interpolation.d.ts +20 -23
- package/out/address/point/interpolation.d.ts.map +1 -1
- package/out/address/point/interpolation.js +42 -39
- package/out/address/point/interpolation.js.map +1 -1
- package/out/address/point/schema.d.ts +12 -35
- package/out/address/point/schema.d.ts.map +1 -1
- package/out/address/point/schema.js +3 -19
- package/out/address/point/schema.js.map +1 -1
- package/out/address/point.d.ts +57 -0
- package/out/address/point.d.ts.map +1 -0
- package/out/address/point.js +206 -0
- package/out/address/point.js.map +1 -0
- package/out/{address/index.js → address.d.ts} +2 -2
- package/out/address.d.ts.map +1 -0
- package/out/{address/index.d.ts → address.js} +2 -2
- package/out/address.js.map +1 -0
- package/out/ancestry/backfill.d.ts +11 -64
- package/out/ancestry/backfill.d.ts.map +1 -1
- package/out/ancestry/backfill.js +14 -74
- package/out/ancestry/backfill.js.map +1 -1
- package/out/ancestry.d.ts +49 -0
- package/out/ancestry.d.ts.map +1 -0
- package/out/ancestry.js +59 -0
- package/out/ancestry.js.map +1 -0
- package/out/build/candidate.d.ts +82 -112
- package/out/build/candidate.d.ts.map +1 -1
- package/out/build/candidate.js +17 -100
- package/out/build/candidate.js.map +1 -1
- package/out/build/population-corrections.d.ts +52 -0
- package/out/build/population-corrections.d.ts.map +1 -0
- package/out/build/population-corrections.js +40 -0
- package/out/build/population-corrections.js.map +1 -0
- package/out/build/slim.d.ts +8 -44
- package/out/build/slim.d.ts.map +1 -1
- package/out/build/slim.js +33 -94
- package/out/build/slim.js.map +1 -1
- package/out/candidate/alias-bags.d.ts +6 -6
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +16 -17
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/ancestors/schema.d.ts +73 -57
- package/out/candidate/ancestors/schema.d.ts.map +1 -1
- package/out/candidate/ancestors/schema.js +56 -48
- package/out/candidate/ancestors/schema.js.map +1 -1
- package/out/candidate/ancestors/sidecar.d.ts +3 -8
- package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
- package/out/candidate/ancestors/sidecar.js +15 -22
- package/out/candidate/ancestors/sidecar.js.map +1 -1
- package/out/candidate/country-display-names.d.ts +5 -15
- package/out/candidate/country-display-names.d.ts.map +1 -1
- package/out/candidate/country-display-names.js +6 -20
- package/out/candidate/country-display-names.js.map +1 -1
- package/out/candidate/extract-fold.d.ts +7 -15
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +5 -47
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/candidate/fts.d.ts +5 -23
- package/out/candidate/fts.d.ts.map +1 -1
- package/out/candidate/fts.js +5 -23
- package/out/candidate/fts.js.map +1 -1
- package/out/candidate/importance.d.ts +60 -65
- package/out/candidate/importance.d.ts.map +1 -1
- package/out/candidate/importance.js +56 -63
- package/out/candidate/importance.js.map +1 -1
- package/out/candidate/lookup.d.ts +18 -55
- package/out/candidate/lookup.d.ts.map +1 -1
- package/out/candidate/lookup.js +82 -385
- package/out/candidate/lookup.js.map +1 -1
- package/out/candidate/name-roles.d.ts +9 -21
- package/out/candidate/name-roles.d.ts.map +1 -1
- package/out/candidate/name-roles.js +15 -33
- package/out/candidate/name-roles.js.map +1 -1
- package/out/candidate/own-name.d.ts +23 -25
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +35 -32
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate/place-attrs.d.ts +20 -13
- package/out/candidate/place-attrs.d.ts.map +1 -1
- package/out/candidate/place-attrs.js +4 -5
- package/out/candidate/place-attrs.js.map +1 -1
- package/out/candidate/schema.d.ts +27 -62
- package/out/candidate/schema.d.ts.map +1 -1
- package/out/candidate/schema.js +10 -22
- package/out/candidate/schema.js.map +1 -1
- package/out/candidate/scoring.d.ts +7 -8
- package/out/candidate/scoring.d.ts.map +1 -1
- package/out/candidate/scoring.js +52 -89
- package/out/candidate/scoring.js.map +1 -1
- package/out/capital-schema.d.ts +21 -17
- package/out/capital-schema.d.ts.map +1 -1
- package/out/capital-schema.js +17 -15
- package/out/capital-schema.js.map +1 -1
- package/out/capitals.d.ts +27 -25
- package/out/capitals.d.ts.map +1 -1
- package/out/capitals.js +23 -21
- package/out/capitals.js.map +1 -1
- package/out/coincident-roles.d.ts +60 -43
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +72 -50
- package/out/coincident-roles.js.map +1 -1
- package/out/convention/schema.d.ts +14 -14
- package/out/convention/schema.d.ts.map +1 -1
- package/out/convention/schema.js +6 -4
- package/out/convention/schema.js.map +1 -1
- package/out/convention.d.ts +97 -0
- package/out/convention.d.ts.map +1 -0
- package/out/convention.js +81 -0
- package/out/convention.js.map +1 -0
- package/out/coverage-manifest-schema.d.ts +34 -39
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +25 -34
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +16 -38
- package/out/currency-backfill.d.ts.map +1 -1
- package/out/currency-backfill.js +19 -46
- package/out/currency-backfill.js.map +1 -1
- package/out/env.d.ts +1 -1
- package/out/env.js +1 -1
- package/out/exact-match.d.ts +19 -10
- package/out/exact-match.d.ts.map +1 -1
- package/out/exact-match.js +32 -21
- package/out/exact-match.js.map +1 -1
- package/out/extracts.d.ts +66 -55
- package/out/extracts.d.ts.map +1 -1
- package/out/extracts.js +63 -55
- package/out/extracts.js.map +1 -1
- package/out/fst/autocomplete.d.ts +29 -21
- package/out/fst/autocomplete.d.ts.map +1 -1
- package/out/fst/autocomplete.js +39 -28
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +37 -41
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.d.ts +4 -4
- package/out/fst/deserialize-web.d.ts.map +1 -1
- package/out/fst/deserialize-web.js +15 -16
- package/out/fst/deserialize-web.js.map +1 -1
- package/out/fst/format.d.ts +41 -23
- package/out/fst/format.d.ts.map +1 -1
- package/out/fst/format.js +41 -23
- package/out/fst/format.js.map +1 -1
- package/out/fst/freshness.d.ts +19 -81
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +31 -94
- package/out/fst/freshness.js.map +1 -1
- package/out/fst/matcher.d.ts +5 -4
- package/out/fst/matcher.d.ts.map +1 -1
- package/out/fst/matcher.js +2 -2
- package/out/fst/matcher.js.map +1 -1
- package/out/fst/serialize.d.ts +28 -28
- package/out/fst/serialize.d.ts.map +1 -1
- package/out/fst/serialize.js +53 -71
- package/out/fst/serialize.js.map +1 -1
- package/out/fst/types.d.ts +78 -74
- package/out/fst/types.d.ts.map +1 -1
- package/out/fst/types.js +1 -4
- package/out/fst/types.js.map +1 -1
- package/out/{fst/index.js → fst.d.ts} +2 -1
- package/out/fst.d.ts.map +1 -0
- package/out/{fst/index.d.ts → fst.js} +2 -1
- package/out/fst.js.map +1 -0
- package/out/fts/query.d.ts +2 -27
- package/out/fts/query.d.ts.map +1 -1
- package/out/fts/query.js +5 -36
- package/out/fts/query.js.map +1 -1
- package/out/fts.d.ts +126 -0
- package/out/fts.d.ts.map +1 -0
- package/out/fts.js +188 -0
- package/out/fts.js.map +1 -0
- package/out/geonames/aliases.d.ts +26 -62
- package/out/geonames/aliases.d.ts.map +1 -1
- package/out/geonames/aliases.js +45 -117
- package/out/geonames/aliases.js.map +1 -1
- package/out/geonames/postal.d.ts +84 -44
- package/out/geonames/postal.d.ts.map +1 -1
- package/out/geonames/postal.js +115 -73
- package/out/geonames/postal.js.map +1 -1
- package/out/{geonames/index.js → geonames.d.ts} +1 -1
- package/out/geonames.d.ts.map +1 -0
- package/out/{geonames/index.d.ts → geonames.js} +1 -1
- package/out/geonames.js.map +1 -0
- package/out/index.d.ts +9 -8
- package/out/index.d.ts.map +1 -1
- package/out/index.js +6 -5
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +18 -55
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +27 -69
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +23 -51
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +136 -234
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts +2 -2
- package/out/name-score.d.ts.map +1 -1
- package/out/name-score.js +9 -7
- package/out/name-score.js.map +1 -1
- package/out/nsul/lookup.d.ts +33 -34
- package/out/nsul/lookup.d.ts.map +1 -1
- package/out/nsul/lookup.js +26 -40
- package/out/nsul/lookup.js.map +1 -1
- package/out/nsul/schema.d.ts +16 -43
- package/out/nsul/schema.d.ts.map +1 -1
- package/out/nsul/schema.js +9 -35
- package/out/nsul/schema.js.map +1 -1
- package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
- package/out/nsul.d.ts.map +1 -0
- package/out/{nsul/index.js → nsul.js} +1 -1
- package/out/nsul.js.map +1 -0
- package/out/paths.d.ts +124 -0
- package/out/paths.d.ts.map +1 -0
- package/out/paths.js +104 -0
- package/out/paths.js.map +1 -0
- package/out/place-importance-schema.d.ts +51 -143
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +28 -143
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts +40 -47
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +69 -96
- package/out/poi/lookup.js.map +1 -1
- package/out/poi/schema.d.ts +41 -25
- package/out/poi/schema.d.ts.map +1 -1
- package/out/poi/schema.js +25 -17
- package/out/poi/schema.js.map +1 -1
- package/out/{poi/index.js → poi.d.ts} +1 -1
- package/out/poi.d.ts.map +1 -0
- package/out/{poi/index.d.ts → poi.js} +1 -1
- package/out/poi.js.map +1 -0
- package/out/polygon-schema.d.ts +10 -6
- package/out/polygon-schema.d.ts.map +1 -1
- package/out/polygon-schema.js +2 -2
- package/out/polygon-schema.js.map +1 -1
- package/out/postal/city/alias/lookup.d.ts +20 -23
- package/out/postal/city/alias/lookup.d.ts.map +1 -1
- package/out/postal/city/alias/lookup.js +17 -30
- package/out/postal/city/alias/lookup.js.map +1 -1
- package/out/postal/city/alias/schema.d.ts +14 -10
- package/out/postal/city/alias/schema.d.ts.map +1 -1
- package/out/postal/city/alias/schema.js +10 -8
- package/out/postal/city/alias/schema.js.map +1 -1
- package/out/postal/city/candidate-schema.d.ts +25 -19
- package/out/postal/city/candidate-schema.d.ts.map +1 -1
- package/out/postal/city/candidate-schema.js +21 -17
- package/out/postal/city/candidate-schema.js.map +1 -1
- package/out/{postal/index.d.ts → postal.d.ts} +1 -1
- package/out/postal.d.ts.map +1 -0
- package/out/{postal/index.js → postal.js} +1 -1
- package/out/postal.js.map +1 -0
- package/out/postcode-point-lookup.d.ts +6 -24
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +5 -17
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/primary-preference.d.ts +38 -92
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +15 -99
- package/out/primary-preference.js.map +1 -1
- package/out/proximity-rerank.d.ts +41 -30
- package/out/proximity-rerank.d.ts.map +1 -1
- package/out/proximity-rerank.js +38 -29
- package/out/proximity-rerank.js.map +1 -1
- package/out/ranking-weights.d.ts +98 -64
- package/out/ranking-weights.d.ts.map +1 -1
- package/out/ranking-weights.js +26 -25
- package/out/ranking-weights.js.map +1 -1
- package/out/region-keys.d.ts +50 -31
- package/out/region-keys.d.ts.map +1 -1
- package/out/region-keys.js +68 -40
- package/out/region-keys.js.map +1 -1
- package/out/reverse.d.ts +78 -50
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +98 -61
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +72 -49
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +4 -5
- package/out/schema.js.map +1 -1
- package/out/search-fetch.d.ts +16 -12
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +83 -63
- package/out/search-fetch.js.map +1 -1
- package/out/sqlite/convention-source.d.ts +12 -16
- package/out/sqlite/convention-source.d.ts.map +1 -1
- package/out/sqlite/convention-source.js +13 -22
- package/out/sqlite/convention-source.js.map +1 -1
- package/out/sqlite/utils.d.ts +19 -19
- package/out/sqlite/utils.d.ts.map +1 -1
- package/out/sqlite/utils.js +14 -15
- package/out/sqlite/utils.js.map +1 -1
- package/out/street/centroid/schema.d.ts +37 -42
- package/out/street/centroid/schema.d.ts.map +1 -1
- package/out/street/centroid/schema.js +9 -21
- package/out/street/centroid/schema.js.map +1 -1
- package/out/street/centroid.d.ts +30 -0
- package/out/street/centroid.d.ts.map +1 -0
- package/out/street/{centroid/index.js → centroid.js} +13 -24
- package/out/street/centroid.js.map +1 -0
- package/out/street/morphology/fst/builder.d.ts +20 -10
- package/out/street/morphology/fst/builder.d.ts.map +1 -1
- package/out/street/morphology/fst/builder.js +44 -34
- package/out/street/morphology/fst/builder.js.map +1 -1
- package/out/street/morphology/fst/loader.d.ts +33 -26
- package/out/street/morphology/fst/loader.d.ts.map +1 -1
- package/out/street/morphology/fst/loader.js +23 -22
- package/out/street/morphology/fst/loader.js.map +1 -1
- package/out/street/name-lookup.d.ts +24 -18
- package/out/street/name-lookup.d.ts.map +1 -1
- package/out/street/name-lookup.js +34 -35
- package/out/street/name-lookup.js.map +1 -1
- package/out/street/normalize.d.ts +35 -136
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +77 -194
- package/out/street/normalize.js.map +1 -1
- package/out/street/segment-schema.d.ts +79 -39
- package/out/street/segment-schema.d.ts.map +1 -1
- package/out/street/segment-schema.js +71 -21
- package/out/street/segment-schema.js.map +1 -1
- package/out/{street/index.js → street.d.ts} +2 -2
- package/out/street.d.ts.map +1 -0
- package/out/{street/index.d.ts → street.js} +2 -2
- package/out/street.js.map +1 -0
- package/out/types.d.ts +84 -149
- package/out/types.d.ts.map +1 -1
- package/out/types.js +0 -6
- package/out/types.js.map +1 -1
- package/out/unified-schema.d.ts +8 -12
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +29 -29
- package/out/unified-schema.js.map +1 -1
- package/out/uprn/existence.d.ts +28 -17
- package/out/uprn/existence.d.ts.map +1 -1
- package/out/uprn/existence.js +26 -16
- package/out/uprn/existence.js.map +1 -1
- package/out/uprn/lookup.d.ts +14 -44
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +21 -57
- package/out/uprn/lookup.js.map +1 -1
- package/out/uprn/schema.d.ts +18 -40
- package/out/uprn/schema.d.ts.map +1 -1
- package/out/uprn/schema.js +10 -32
- package/out/uprn/schema.js.map +1 -1
- package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
- package/out/uprn.d.ts.map +1 -0
- package/out/{uprn/index.js → uprn.js} +1 -1
- package/out/uprn.js.map +1 -0
- package/out/weights-overlay-linker.d.ts +138 -144
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +105 -159
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +91 -596
- package/lib/address/point/index.ts +0 -328
- package/lib/convention/index.ts +0 -154
- package/lib/fts/index.ts +0 -361
- package/out/address/index.d.ts.map +0 -1
- package/out/address/index.js.map +0 -1
- package/out/address/point/index.d.ts +0 -53
- package/out/address/point/index.d.ts.map +0 -1
- package/out/address/point/index.js +0 -222
- package/out/address/point/index.js.map +0 -1
- package/out/ancestry/index.d.ts +0 -45
- package/out/ancestry/index.d.ts.map +0 -1
- package/out/ancestry/index.js +0 -55
- package/out/ancestry/index.js.map +0 -1
- package/out/convention/index.d.ts +0 -106
- package/out/convention/index.d.ts.map +0 -1
- package/out/convention/index.js +0 -93
- package/out/convention/index.js.map +0 -1
- package/out/fst/index.d.ts.map +0 -1
- package/out/fst/index.js.map +0 -1
- package/out/fts/index.d.ts +0 -184
- package/out/fts/index.d.ts.map +0 -1
- package/out/fts/index.js +0 -277
- package/out/fts/index.js.map +0 -1
- package/out/geonames/index.d.ts.map +0 -1
- package/out/geonames/index.js.map +0 -1
- package/out/nsul/index.d.ts.map +0 -1
- package/out/nsul/index.js.map +0 -1
- package/out/poi/index.d.ts.map +0 -1
- package/out/poi/index.js.map +0 -1
- package/out/postal/index.d.ts.map +0 -1
- package/out/postal/index.js.map +0 -1
- package/out/street/centroid/index.d.ts +0 -40
- package/out/street/centroid/index.d.ts.map +0 -1
- package/out/street/centroid/index.js.map +0 -1
- package/out/street/index.d.ts.map +0 -1
- package/out/street/index.js.map +0 -1
- package/out/uprn/index.d.ts.map +0 -1
- package/out/uprn/index.js.map +0 -1
- /package/lib/{geonames/index.ts → geonames.ts} +0 -0
- /package/lib/{nsul/index.ts → nsul.ts} +0 -0
- /package/lib/{poi/index.ts → poi.ts} +0 -0
- /package/lib/{postal/index.ts → postal.ts} +0 -0
- /package/lib/{uprn/index.ts → uprn.ts} +0 -0
package/lib/fst/freshness.ts
CHANGED
|
@@ -3,74 +3,41 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* WHY THIS EXISTS. Every FST artifact is a projection of one WOF admin database, and the admin
|
|
9
|
-
* database is a sealed readonly artifact that a rebuild REPLACES. Nothing mechanically tied the two
|
|
10
|
-
* together: the 2026-08-04 admin swap (4.87M rows, ancestry repaired, macrohood/microhood ingested)
|
|
11
|
-
* left `fst-global-priority.bin` at its 2026-05-28 build and the per-locale set at 2026-07-26, and
|
|
12
|
-
* the only way to notice was to compare mtimes by hand. The artifacts kept loading, kept answering
|
|
13
|
-
* queries, and answered them from a gazetteer that no longer exists.
|
|
14
|
-
*
|
|
15
|
-
* `FSTProvenance` already recorded `sourceDB` — the source's PATH, which is exactly the field that
|
|
16
|
-
* cannot change when the bytes behind it do. So the stamp gains the source's IDENTITY (md5 + byte
|
|
17
|
-
* size) and this module compares it. The shape deliberately mirrors
|
|
18
|
-
* `@mailwoman/resolver-wof-sqlite/weights-overlay-linker`'s `pairIndexStaleReason`: one function returning a reason
|
|
19
|
-
* string or `undefined`, so a fact added to the stamp cannot be checked by some callers and not
|
|
20
|
-
* others — which is how three of the four base linkers ended up unable to notice a PIX1 schema bump.
|
|
21
|
-
*
|
|
22
|
-
* FORMAT IS PART OF FRESHNESS. The check compares the serializer version too, not just the source
|
|
23
|
-
* md5. A guard that checks only the source reads a format-obsolete binary as "current" (the R5
|
|
24
|
-
* freshness-guard lesson, format edition), and a file below {@link MIN_STAMPED_FORMAT_VERSION}
|
|
25
|
-
* cannot carry a stamp at all — reported as its own reason rather than silently passing.
|
|
26
|
-
*
|
|
27
|
-
* STALE IS A WARNING, NOT A FAULT. A dev tree with an old FST must still run; the artifact is a
|
|
28
|
-
* decode-time bias list, not a correctness dependency. Callers print {@link formatFSTStaleWarning}
|
|
29
|
-
* and continue.
|
|
6
|
+
* Does an `fst-*.bin` still match the gazetteer it was built from?
|
|
30
7
|
*/
|
|
31
8
|
|
|
32
|
-
import {
|
|
9
|
+
import { readFileRange, readLocalTextFile } from "@mailwoman/core/fs/readers"
|
|
10
|
+
import { pathExists, statPath } from "@mailwoman/core/fs/readers/stat"
|
|
33
11
|
import { writeLocalTextFile } from "@mailwoman/core/fs/writers"
|
|
34
12
|
import { md5File } from "@mailwoman/core/hash"
|
|
35
13
|
import { tryParsingJSON } from "@mailwoman/core/json"
|
|
36
14
|
import { basename, resolvePath, type PathBuilderLike } from "path-ts"
|
|
37
15
|
|
|
38
|
-
import { FST_FORMAT_VERSION } from "#fst/
|
|
16
|
+
import { FST_FORMAT_VERSION } from "#fst/format"
|
|
39
17
|
import type { FSTProvenance } from "#fst/types"
|
|
40
18
|
|
|
41
19
|
/**
|
|
42
|
-
* Fixed header size in bytes
|
|
43
|
-
* because this module reads the header by SEEK (never buffering the file), and the serializer's constant is private to
|
|
44
|
-
* its own read/write pair.
|
|
20
|
+
* Fixed header size in bytes, mirroring `fst-serialize.ts`'s private `HEADER_SIZE`.
|
|
45
21
|
*/
|
|
46
22
|
const HEADER_SIZE = 32
|
|
47
23
|
|
|
48
24
|
/**
|
|
49
|
-
* `"FST\0"` little-endian, the four
|
|
25
|
+
* `"FST\0"` little-endian, the four bytes every artifact opens with.
|
|
50
26
|
*/
|
|
51
27
|
const MAGIC = 0x00_54_53_46
|
|
52
28
|
|
|
53
|
-
/**
|
|
54
|
-
* Byte offset of the u32 trailer offset inside the header (the last of its eight fields).
|
|
55
|
-
*/
|
|
56
29
|
const PROVENANCE_OFFSET_FIELD = 28
|
|
57
30
|
|
|
58
31
|
/**
|
|
59
|
-
* First serializer version carrying the trailing provenance block.
|
|
60
|
-
*
|
|
32
|
+
* First serializer version carrying the trailing provenance block. below it
|
|
33
|
+
* a file has no place to put a stamp.
|
|
61
34
|
*/
|
|
62
35
|
export const MIN_STAMPED_FORMAT_VERSION = 3
|
|
63
36
|
|
|
64
|
-
/**
|
|
65
|
-
* Hex characters in an md5 digest.
|
|
66
|
-
*/
|
|
67
37
|
const MD5_HEX_LENGTH = 32
|
|
68
38
|
|
|
69
39
|
/**
|
|
70
40
|
* What an FST was built from, recorded so a later reader can tell whether that thing still exists.
|
|
71
|
-
*
|
|
72
|
-
* `bytes` is not redundant with `md5` — it is the field that survives a truncated or half-written source and makes the
|
|
73
|
-
* mismatch legible in the warning ("5,273,722,880 → 5,372,076,032" names the rebuild; a hex delta does not).
|
|
74
41
|
*/
|
|
75
42
|
export interface FSTSourceIdentity {
|
|
76
43
|
md5: string
|
|
@@ -79,85 +46,55 @@ export interface FSTSourceIdentity {
|
|
|
79
46
|
|
|
80
47
|
/**
|
|
81
48
|
* The stamp fields a freshness guard reads off an artifact, plus the format version it was written at.
|
|
82
|
-
*
|
|
83
|
-
* Every field is optional because every one of them can be legitimately absent on an artifact that predates it, and the
|
|
84
|
-
* meaning-of-zero rule applies to all of them: absent is a distinct state from "matches", and the reasons below say so
|
|
85
|
-
* in different words.
|
|
86
49
|
*/
|
|
87
50
|
export interface FSTStampFields {
|
|
88
51
|
formatVersion: number
|
|
89
|
-
provenance: FSTProvenance |
|
|
52
|
+
provenance: FSTProvenance | null
|
|
90
53
|
}
|
|
91
54
|
|
|
92
55
|
/**
|
|
93
56
|
* What a caller expects the artifact to have been built from.
|
|
94
|
-
*
|
|
95
|
-
* `exclusionPolicy` is optional and caller-supplied on purpose. The policy id lives in
|
|
96
|
-
* `mailwoman/gazetteer-pipeline/fst.ts` (which depends on this package, not the other way round), so only the caller
|
|
97
|
-
* knows which policy it means — the same split as the pair-index guard, where the format+magnitude half is shared and
|
|
98
|
-
* the source-md5 half stays with the script that knows its sources.
|
|
99
57
|
*/
|
|
100
58
|
export interface FSTExpectation {
|
|
101
59
|
source: FSTSourceIdentity
|
|
102
60
|
/**
|
|
103
|
-
* Serializer version to require
|
|
104
|
-
* cannot forget the format half of the comparison. Override only to check against a specific older floor.
|
|
61
|
+
* Serializer version to require, defaulting to {@link FST_FORMAT_VERSION}.
|
|
105
62
|
*/
|
|
106
|
-
formatVersion?: number
|
|
107
|
-
exclusionPolicy?: string
|
|
63
|
+
formatVersion?: number | null
|
|
64
|
+
exclusionPolicy?: string | null
|
|
108
65
|
}
|
|
109
66
|
|
|
110
67
|
/**
|
|
111
|
-
* Read an artifact's stamp
|
|
112
|
-
* kilobytes. The distinction matters: `fst-global-priority.bin` is 317 MB and this runs on every `yarn test` via the
|
|
113
|
-
* weights linkers, so `readFileSync` + `readFSTProvenance` would trade a freshness guard for a slower test suite and
|
|
114
|
-
* nobody would keep it.
|
|
115
|
-
*
|
|
116
|
-
* Returns `undefined` for a file that is absent, too small, or not an FST at all — none of which is this function's
|
|
117
|
-
* business to diagnose.
|
|
68
|
+
* Read an artifact's stamp without deserializing it.
|
|
118
69
|
*/
|
|
119
|
-
export async function peekFSTStampFields(path:
|
|
120
|
-
if (!(await pathExists(path))) return
|
|
70
|
+
export async function peekFSTStampFields(path: PathBuilderLike): Promise<FSTStampFields | null> {
|
|
71
|
+
if (!(await pathExists(path))) return null
|
|
121
72
|
const size = (await statPath(path)).size
|
|
122
73
|
|
|
123
|
-
if (size < HEADER_SIZE) return
|
|
74
|
+
if (size < HEADER_SIZE) return null
|
|
124
75
|
const header = await readFileRange(path, 0, HEADER_SIZE)
|
|
125
76
|
|
|
126
|
-
if (header.readUInt32LE(0) !== MAGIC) return
|
|
77
|
+
if (header.readUInt32LE(0) !== MAGIC) return null
|
|
127
78
|
const formatVersion = header.readUInt16LE(4)
|
|
128
79
|
|
|
129
|
-
if (formatVersion < MIN_STAMPED_FORMAT_VERSION) return { formatVersion, provenance:
|
|
80
|
+
if (formatVersion < MIN_STAMPED_FORMAT_VERSION) return { formatVersion, provenance: null }
|
|
130
81
|
const trailerStart = header.readUInt32LE(PROVENANCE_OFFSET_FIELD)
|
|
131
82
|
|
|
132
|
-
// 0
|
|
133
|
-
//
|
|
134
|
-
if (trailerStart === 0 || trailerStart + 4 > size) return { formatVersion, provenance:
|
|
83
|
+
// 0 means this build wrote no trailer and past-EOF means truncated.
|
|
84
|
+
// Both read as no stamp.
|
|
85
|
+
if (trailerStart === 0 || trailerStart + 4 > size) return { formatVersion, provenance: null }
|
|
135
86
|
|
|
136
87
|
const jsonLength = (await readFileRange(path, trailerStart, 4)).readUInt32LE(0)
|
|
137
88
|
|
|
138
|
-
if (jsonLength === 0 || trailerStart + 4 + jsonLength > size) return { formatVersion, provenance:
|
|
89
|
+
if (jsonLength === 0 || trailerStart + 4 + jsonLength > size) return { formatVersion, provenance: null }
|
|
139
90
|
|
|
140
91
|
const json = await readFileRange(path, trailerStart + 4, jsonLength)
|
|
141
92
|
|
|
142
|
-
return { formatVersion, provenance: tryParsingJSON<FSTProvenance>(json.toString("utf8")) ??
|
|
93
|
+
return { formatVersion, provenance: tryParsingJSON<FSTProvenance>(json.toString("utf8")) ?? null }
|
|
143
94
|
}
|
|
144
95
|
|
|
145
|
-
/**
|
|
146
|
-
* Re-exported from `@mailwoman/core/hash`. This package's own test imports it from here, and the sidecar convention
|
|
147
|
-
* below is what it is for.
|
|
148
|
-
*/
|
|
149
|
-
|
|
150
96
|
/**
|
|
151
97
|
* The source identity an FST build should stamp, or a check should compare against.
|
|
152
|
-
*
|
|
153
|
-
* Uses the `.md5` sidecar convention the weights linkers already established on this exact file: `<path>.md5` in
|
|
154
|
-
* md5sum(1) format (`<hash> <filename>`), trusted only while its mtime is at least the source's. An older sidecar is
|
|
155
|
-
* recomputed. Without it the admin DB costs 7.3 s per call and the guard would be quietly disabled by whoever noticed
|
|
156
|
-
* `yarn test` got slower.
|
|
157
|
-
*
|
|
158
|
-
* `refreshSidecar` writes the recomputed digest back. Best-effort: a sealed data root or a read-only mount fails the
|
|
159
|
-
* write and the caller still gets its answer, because refusing to check freshness on a read-only tree would be the
|
|
160
|
-
* wrong trade.
|
|
161
98
|
*/
|
|
162
99
|
export async function readWOFSourceIdentity(
|
|
163
100
|
source: PathBuilderLike,
|
|
@@ -170,7 +107,7 @@ export async function readWOFSourceIdentity(
|
|
|
170
107
|
|
|
171
108
|
if (hit) return hit
|
|
172
109
|
const sidecarPath = `${path}.md5`
|
|
173
|
-
let md5: string |
|
|
110
|
+
let md5: string | null = null
|
|
174
111
|
|
|
175
112
|
if (await pathExists(sidecarPath)) {
|
|
176
113
|
const sidecarStats = await statPath(sidecarPath)
|
|
@@ -191,7 +128,7 @@ export async function readWOFSourceIdentity(
|
|
|
191
128
|
try {
|
|
192
129
|
await writeLocalTextFile(`${md5} ${basename(path)}\n`, sidecarPath)
|
|
193
130
|
} catch {
|
|
194
|
-
// Read-only data root
|
|
131
|
+
// Read-only data root: the digest is still correct, only the cache write failed.
|
|
195
132
|
}
|
|
196
133
|
}
|
|
197
134
|
}
|
|
@@ -202,21 +139,12 @@ export async function readWOFSourceIdentity(
|
|
|
202
139
|
return identity
|
|
203
140
|
}
|
|
204
141
|
|
|
205
|
-
/**
|
|
206
|
-
* Memo for {@link readWOFSourceIdentity}, keyed on (path, mtimeMs, size) — NOT on path alone, for the same reason
|
|
207
|
-
* `computeSurfaceCountryCounts` isn't: the admin DB is a sealed artifact that a rebuild REPLACES, so a path-only memo
|
|
208
|
-
* would serve a stale digest against a new file for the life of the process.
|
|
209
|
-
*/
|
|
210
142
|
const sourceIdentityMemo = new Map<string, FSTSourceIdentity>()
|
|
211
143
|
|
|
212
144
|
/**
|
|
213
|
-
* Why an FST artifact is stale against `expected`, or `
|
|
214
|
-
*
|
|
215
|
-
* The order is deliberate: FORMAT first (a version-obsolete file is stale whatever its source says), then the presence
|
|
216
|
-
* of a stamp, then the source identity, then the build policy. Each returns prose a reader can act on — the reasons are
|
|
217
|
-
* printed verbatim into {@link formatFSTStaleWarning}.
|
|
145
|
+
* Why an FST artifact is stale against `expected`, or `null` when it still matches.
|
|
218
146
|
*/
|
|
219
|
-
export function fstStaleReason(fields: FSTStampFields |
|
|
147
|
+
export function fstStaleReason(fields: FSTStampFields | null, expected: FSTExpectation): string | null {
|
|
220
148
|
if (!fields) return "unreadable or not an FST artifact"
|
|
221
149
|
const requiredFormat = expected.formatVersion ?? FST_FORMAT_VERSION
|
|
222
150
|
|
|
@@ -240,24 +168,19 @@ export function fstStaleReason(fields: FSTStampFields | undefined, expected: FST
|
|
|
240
168
|
return `source db ${provenance.sourceDBMD5.slice(0, 8)} → ${expected.source.md5.slice(0, 8)} (built ${provenance.builtAt})`
|
|
241
169
|
}
|
|
242
170
|
|
|
243
|
-
|
|
244
|
-
// against a different file than it was hashed from. Cheap to check, and it never fires by accident.
|
|
245
|
-
if (provenance.sourceDBBytes !== undefined && provenance.sourceDBBytes !== expected.source.bytes) {
|
|
171
|
+
if (provenance.sourceDBBytes != null && provenance.sourceDBBytes !== expected.source.bytes) {
|
|
246
172
|
return `source db size ${provenance.sourceDBBytes} → ${expected.source.bytes} at a matching md5 — one of the two is misrecorded`
|
|
247
173
|
}
|
|
248
174
|
|
|
249
|
-
if (expected.exclusionPolicy
|
|
175
|
+
if (expected.exclusionPolicy && provenance.exclusionPolicy !== expected.exclusionPolicy) {
|
|
250
176
|
return `exclusion policy ${provenance.exclusionPolicy ?? "(none)"} → ${expected.exclusionPolicy}`
|
|
251
177
|
}
|
|
252
178
|
|
|
253
|
-
return
|
|
179
|
+
return null
|
|
254
180
|
}
|
|
255
181
|
|
|
256
182
|
/**
|
|
257
|
-
* The whole check, for a caller
|
|
258
|
-
*
|
|
259
|
-
* Returns `undefined` when the artifact is current OR when it is absent — an absent artifact is a different problem
|
|
260
|
-
* with a different message, and every existing caller already reports it in place.
|
|
183
|
+
* The whole check, for a caller with a path and a source DB that wants a warning string or none.
|
|
261
184
|
*/
|
|
262
185
|
export async function fstFreshnessWarning({
|
|
263
186
|
fstPath,
|
|
@@ -266,34 +189,34 @@ export async function fstFreshnessWarning({
|
|
|
266
189
|
exclusionPolicy,
|
|
267
190
|
rebuildCommand,
|
|
268
191
|
}: {
|
|
269
|
-
fstPath:
|
|
270
|
-
sourceDBPath:
|
|
271
|
-
formatVersion?: number
|
|
272
|
-
exclusionPolicy?: string
|
|
192
|
+
fstPath: PathBuilderLike
|
|
193
|
+
sourceDBPath: PathBuilderLike
|
|
194
|
+
formatVersion?: number | null
|
|
195
|
+
exclusionPolicy?: string | null
|
|
273
196
|
rebuildCommand: string
|
|
274
|
-
}): Promise<string |
|
|
275
|
-
if (!(await pathExists(fstPath)) || !(await pathExists(sourceDBPath))) return
|
|
197
|
+
}): Promise<string | null> {
|
|
198
|
+
if (!(await pathExists(fstPath)) || !(await pathExists(sourceDBPath))) return null
|
|
276
199
|
|
|
277
200
|
const reason = fstStaleReason(await peekFSTStampFields(fstPath), {
|
|
278
201
|
source: await readWOFSourceIdentity(sourceDBPath),
|
|
279
|
-
|
|
280
|
-
|
|
202
|
+
formatVersion: formatVersion ?? null,
|
|
203
|
+
exclusionPolicy: exclusionPolicy ?? null,
|
|
281
204
|
})
|
|
282
205
|
|
|
283
|
-
return reason
|
|
206
|
+
return reason ? formatFSTStaleWarning({ fstPath, reason, rebuildCommand }) : null
|
|
284
207
|
}
|
|
285
208
|
|
|
286
209
|
/**
|
|
287
|
-
* The one warning format, so `grep -r "FST
|
|
210
|
+
* The one warning format, so `grep -r "FST stale"` finds every site that emits one.
|
|
288
211
|
*/
|
|
289
212
|
export function formatFSTStaleWarning({
|
|
290
213
|
fstPath,
|
|
291
214
|
reason,
|
|
292
215
|
rebuildCommand,
|
|
293
216
|
}: {
|
|
294
|
-
fstPath:
|
|
217
|
+
fstPath: PathBuilderLike
|
|
295
218
|
reason: string
|
|
296
219
|
rebuildCommand: string
|
|
297
220
|
}): string {
|
|
298
|
-
return `WARNING: FST STALE — ${fstPath}: ${reason}. Rebuild with: ${rebuildCommand}`
|
|
221
|
+
return `WARNING: FST STALE — ${fstPath.toString()}: ${reason}. Rebuild with: ${rebuildCommand}`
|
|
299
222
|
}
|
package/lib/fst/matcher.ts
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* arrays at accepting states.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type { AncestrieMatch } from "@mailwoman/ancestrie"
|
|
12
|
+
|
|
13
|
+
import type { FSTContinuation, FSTQueryResult, PlaceEntry } from "#fst/types"
|
|
12
14
|
|
|
13
15
|
interface FSTNode {
|
|
14
16
|
edges: Map<string, number>
|
|
@@ -36,7 +38,7 @@ export class FSTMatcher {
|
|
|
36
38
|
return count
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
walk(tokens: string[]):
|
|
41
|
+
walk(tokens: string[]): AncestrieMatch | null {
|
|
40
42
|
let stateID = 0
|
|
41
43
|
|
|
42
44
|
for (const token of tokens) {
|
|
@@ -54,7 +56,7 @@ export class FSTMatcher {
|
|
|
54
56
|
return { stateID, accepted: node.places.length > 0, depth: tokens.length }
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
walkFrom(prev:
|
|
59
|
+
walkFrom(prev: AncestrieMatch, token: string): AncestrieMatch | null {
|
|
58
60
|
const node = this.nodes[prev.stateID]
|
|
59
61
|
|
|
60
62
|
if (!node) return null
|
|
@@ -143,7 +145,7 @@ export class FSTMatcher {
|
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
/**
|
|
146
|
-
* Normalize text into FST tokens: lowercase,
|
|
148
|
+
* Normalize text into FST tokens: lowercase, nfkc, strip punctuation, split on whitespace.
|
|
147
149
|
*/
|
|
148
150
|
export function normalizeTokens(text: string): string[] {
|
|
149
151
|
return text
|
|
@@ -151,7 +153,7 @@ export function normalizeTokens(text: string): string[] {
|
|
|
151
153
|
.toLowerCase()
|
|
152
154
|
.replaceAll(/[\p{P}\p{S}]/gu, "")
|
|
153
155
|
.split(/\s+/)
|
|
154
|
-
.filter((t) => t.length
|
|
156
|
+
.filter((t) => t.length)
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
export type { FSTNode }
|
package/lib/fst/serialize.ts
CHANGED
|
@@ -2,38 +2,27 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file Binary serialization for the FST gazetteer.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* All integers are little-endian. The writer emits the current version. The reader accepts every earlier version.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - Header, 32 bytes: magic `FST\0`, version u16, flags u16 (bit 0 means `crossCountryBranches` is present),
|
|
10
|
+
* The header stores five u32 counts (stateCount, edgeCount, placeCount, stringCount, stringBytes).
|
|
11
|
+
* Its provenance offset is u32. The value 0 means the trailer is absent.
|
|
12
|
+
* - The string table contains `stringCount + 1` u32 offsets, with the final offset as a sentinel, followed by the UTF-8 data.
|
|
13
|
+
* - State table, 16 bytes per state: edgeStart u32, placeStart u32, edgeCount u32, placeCount u32. Before version
|
|
14
|
+
* 4 the entry is 12 bytes with u16 counts.
|
|
15
|
+
* - Edge table, 8 bytes per edge: stringIdx u32, targetState u32.
|
|
16
|
+
* - Place table, 60 bytes per place: wofID u32, placetypeIdx u8, chainLen u8, crossCountryBranches u8,
|
|
17
|
+
* placeFlags u8 (bit 0 means `encyclopedic` is present), nameIdx u32, referential f32, lat f32, lon f32.
|
|
18
|
+
* Each entry then stores eight u32 parent ids. Version 5 added encyclopedic f32. Earlier entries contain 56 bytes.
|
|
19
|
+
* Those entries have no encyclopedic score.
|
|
20
|
+
* - Provenance trailer: a u32 length followed by JSON.
|
|
12
21
|
*
|
|
13
|
-
*
|
|
14
|
-
* stringBytes] concatenated UTF-8
|
|
15
|
-
*
|
|
16
|
-
* STATE TABLE [stateCount × 12 bytes] edgeStart u32 index into edge table placeStart u32 index into
|
|
17
|
-
* place table edgeCount u16 placeCount u16
|
|
18
|
-
*
|
|
19
|
-
* EDGE TABLE [edgeCount × 8 bytes] stringIdx u32 index into string table targetState u32
|
|
20
|
-
*
|
|
21
|
-
* PLACE TABLE [placeCount × 60 bytes at V5, 56 below] wofID u32 placetypeIdx u8 index into
|
|
22
|
-
* PLACETYPE_ORDER chainLen u8 0..8 crossCountryBranches u8 (header flags bit0 enables the read)
|
|
23
|
-
* placeFlags u8 (V5; bit0 = encyclopedic present) nameIdx u32 index into string table referential f32
|
|
24
|
-
* population-anchored likelihood [0,1] — was the conflated `importance` (V2–V4), was population u32
|
|
25
|
-
* (V1) lat f32 lon f32 chain [u32; 8] parent chain (unused slots = 0) encyclopedic f32 (V5 only;
|
|
26
|
-
* read only when placeFlags bit0 is set)
|
|
27
|
-
*
|
|
28
|
-
* THE V5 BUMP IS THE TWO-SCORE SPLIT (ROAD_TO_V9 §2 R1). Through V4 one float carried whichever score
|
|
29
|
-
* the source database held, and nothing in the bytes said which — so a reader could not tell a
|
|
30
|
-
* population proxy from a Wikipedia score. V5 names the ranking score `referential` and gives the
|
|
31
|
-
* encyclopedic signal its own slot plus a PER-PLACE presence bit, because most places have no
|
|
32
|
-
* Wikipedia article and a 0 there would be a fact nobody recorded. `fst-freshness.ts` reports every
|
|
33
|
-
* V4-and-below artifact as format-stale for exactly this reason: its single float is unattributable.
|
|
22
|
+
* Presence bits keep a missing value distinct from a stored zero.
|
|
34
23
|
*/
|
|
35
24
|
|
|
36
|
-
import { tryParsingJSON } from "@mailwoman/core/json"
|
|
25
|
+
import { tryParsingJSON, stringifyJSON } from "@mailwoman/core/json"
|
|
37
26
|
|
|
38
27
|
import {
|
|
39
28
|
EDGE_ENTRY_SIZE,
|
|
@@ -55,16 +44,12 @@ import type { FSTNode } from "#fst/matcher"
|
|
|
55
44
|
import { FSTMatcher } from "#fst/matcher"
|
|
56
45
|
import type { FSTProvenance, PlaceEntry } from "#fst/types"
|
|
57
46
|
|
|
58
|
-
export { FST_FORMAT_VERSION } from "#fst/format"
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* File magic as a Buffer, for the Node-side prefix comparison.
|
|
62
|
-
*/
|
|
63
47
|
const MAGIC = Buffer.from(FST_MAGIC_BYTES)
|
|
64
48
|
|
|
65
49
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
50
|
+
* The longest parent chain stored per place.
|
|
51
|
+
*
|
|
52
|
+
* The writer keeps the first eight valid parent ids and drops the rest.
|
|
68
53
|
*/
|
|
69
54
|
const MAX_CHAIN_LEN = 8
|
|
70
55
|
|
|
@@ -74,10 +59,12 @@ for (let i = 0; i < PLACETYPE_ORDER.length; i++) {
|
|
|
74
59
|
placetypeToIdx.set(PLACETYPE_ORDER[i]!, i)
|
|
75
60
|
}
|
|
76
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Serializes a matcher, with optional provenance, into the current FST binary format.
|
|
64
|
+
*/
|
|
77
65
|
export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): Buffer {
|
|
78
66
|
const nodes = matcher.toNodes() as FSTNode[]
|
|
79
67
|
|
|
80
|
-
// Intern state-edge strings before serializing their table.
|
|
81
68
|
const stringMap = new Map<string, number>()
|
|
82
69
|
const strings: string[] = []
|
|
83
70
|
|
|
@@ -106,7 +93,6 @@ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): B
|
|
|
106
93
|
const encodedStrings = strings.map((s) => Buffer.from(s, "utf8"))
|
|
107
94
|
const stringBytes = encodedStrings.reduce((sum, b) => sum + b.length, 0)
|
|
108
95
|
|
|
109
|
-
// Count each serialized record class.
|
|
110
96
|
let totalEdges = 0
|
|
111
97
|
let totalPlaces = 0
|
|
112
98
|
|
|
@@ -115,23 +101,20 @@ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): B
|
|
|
115
101
|
totalPlaces += node.places.length
|
|
116
102
|
}
|
|
117
103
|
|
|
118
|
-
// Allocate the complete binary buffer.
|
|
119
104
|
const stringTableSize = (strings.length + 1) * 4 + stringBytes
|
|
120
105
|
const stateTableSize = nodes.length * WIDE_STATE_ENTRY_SIZE
|
|
121
106
|
const edgeTableSize = totalEdges * EDGE_ENTRY_SIZE
|
|
122
107
|
const placeTableSize = totalPlaces * SPLIT_PLACE_ENTRY_SIZE
|
|
123
|
-
const provenanceJson = provenance ? Buffer.from(
|
|
108
|
+
const provenanceJson = provenance ? Buffer.from(stringifyJSON(provenance), "utf8") : null
|
|
124
109
|
const provenanceSize = provenanceJson ? 4 + provenanceJson.length : 0
|
|
125
110
|
const binarySize = HEADER_SIZE + stringTableSize + stateTableSize + edgeTableSize + placeTableSize
|
|
126
111
|
const totalSize = binarySize + provenanceSize
|
|
127
112
|
const buf = Buffer.alloc(totalSize)
|
|
128
113
|
let pos = 0
|
|
129
114
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
// put: pre-ambiguity artifacts read flags=0 → readers expose `undefined`, never a fake 0.
|
|
134
|
-
const hasAmbiguity = nodes.some((n) => n.places.some((p) => p.crossCountryBranches !== undefined))
|
|
115
|
+
// Flag bit 0 marks that place rows include `crossCountryBranches`.
|
|
116
|
+
// Readers of a file without it report `undefined`.
|
|
117
|
+
const hasAmbiguity = nodes.some((n) => n.places.some((p) => p.crossCountryBranches != null))
|
|
135
118
|
MAGIC.copy(buf, pos)
|
|
136
119
|
pos += 4
|
|
137
120
|
buf.writeUInt16LE(FST_FORMAT_VERSION, pos)
|
|
@@ -151,7 +134,6 @@ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): B
|
|
|
151
134
|
buf.writeUInt32LE(provenanceJson ? binarySize : 0, pos)
|
|
152
135
|
pos += 4
|
|
153
136
|
|
|
154
|
-
// Read the interned string table.
|
|
155
137
|
let strOffset = 0
|
|
156
138
|
|
|
157
139
|
for (const encoded of encodedStrings) {
|
|
@@ -160,17 +142,15 @@ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): B
|
|
|
160
142
|
strOffset += encoded.length
|
|
161
143
|
}
|
|
162
144
|
|
|
145
|
+
// The final offset is the sentinel that ends the last string.
|
|
163
146
|
buf.writeUInt32LE(strOffset, pos)
|
|
164
147
|
pos += 4
|
|
165
148
|
|
|
166
|
-
// sentinel
|
|
167
|
-
|
|
168
149
|
for (const encoded of encodedStrings) {
|
|
169
150
|
encoded.copy(buf, pos)
|
|
170
151
|
pos += encoded.length
|
|
171
152
|
}
|
|
172
153
|
|
|
173
|
-
// Read state, edge, and place tables.
|
|
174
154
|
const stateTableStart = pos
|
|
175
155
|
const edgeTableStart = stateTableStart + stateTableSize
|
|
176
156
|
const placeTableStart = edgeTableStart + edgeTableSize
|
|
@@ -197,17 +177,16 @@ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): B
|
|
|
197
177
|
|
|
198
178
|
for (const place of node.places) {
|
|
199
179
|
const pp = placeTableStart + placeIdx * SPLIT_PLACE_ENTRY_SIZE
|
|
200
|
-
//
|
|
180
|
+
// WOF uses negative parent ids such as -1 as sentinels.
|
|
201
181
|
const validChain = place.parentChain.filter((id) => id > 0)
|
|
202
182
|
const chainLen = Math.min(validChain.length, MAX_CHAIN_LEN)
|
|
203
183
|
buf.writeUInt32LE(place.wofID, pp)
|
|
204
184
|
buf.writeUInt8(placetypeToIdx.get(place.placetype) ?? 0, pp + 4)
|
|
205
185
|
buf.writeUInt8(chainLen, pp + 5)
|
|
206
|
-
// Former _pad: byte 0 = crossCountryBranches (header flags bit0 enables the read), byte 1 = v5 placeFlags.
|
|
207
186
|
buf.writeUInt8(hasAmbiguity ? Math.min(place.crossCountryBranches ?? 0, 255) : 0, pp + 6)
|
|
208
|
-
// An absent encyclopedic score writes flag 0
|
|
209
|
-
//
|
|
210
|
-
const hasEncyclopedic = place.encyclopedic
|
|
187
|
+
// An absent encyclopedic score writes flag 0 and a 0.0 float.
|
|
188
|
+
// Readers skip the float when the flag is 0.
|
|
189
|
+
const hasEncyclopedic = place.encyclopedic != null
|
|
211
190
|
buf.writeUInt8(hasEncyclopedic ? PLACE_FLAG_HAS_ENCYCLOPEDIC : 0, pp + 7)
|
|
212
191
|
buf.writeUInt32LE(intern(place.name), pp + 8)
|
|
213
192
|
buf.writeFloatLE(place.referential, pp + 12)
|
|
@@ -218,7 +197,7 @@ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): B
|
|
|
218
197
|
buf.writeUInt32LE(ci < chainLen ? validChain[ci]! : 0, pp + 24 + ci * 4)
|
|
219
198
|
}
|
|
220
199
|
|
|
221
|
-
buf.writeFloatLE(
|
|
200
|
+
buf.writeFloatLE(place.encyclopedic ?? 0, pp + ENCYCLOPEDIC_OFFSET)
|
|
222
201
|
|
|
223
202
|
placeIdx++
|
|
224
203
|
}
|
|
@@ -233,8 +212,12 @@ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): B
|
|
|
233
212
|
return buf
|
|
234
213
|
}
|
|
235
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Deserializes an FST binary of any supported version into a matcher.
|
|
217
|
+
*
|
|
218
|
+
* @throws When the buffer is too small, has the wrong magic, or has an unsupported version.
|
|
219
|
+
*/
|
|
236
220
|
export function deserializeFST(buf: Buffer): FSTMatcher {
|
|
237
|
-
// Verify the versioned binary header.
|
|
238
221
|
if (buf.length < HEADER_SIZE) throw new Error("FST buffer too small for header")
|
|
239
222
|
|
|
240
223
|
if (!buf.subarray(0, 4).equals(MAGIC)) throw new Error("FST magic mismatch")
|
|
@@ -246,7 +229,6 @@ export function deserializeFST(buf: Buffer): FSTMatcher {
|
|
|
246
229
|
|
|
247
230
|
const isV2 = version >= 2
|
|
248
231
|
const isSplit = version >= VERSION_TWO_SCORE_SPLIT
|
|
249
|
-
// flags bit0 (survey #4): place rows carry surface-ambiguity data in the former _pad byte.
|
|
250
232
|
const hasAmbiguity = (buf.readUInt16LE(6) & 1) === 1
|
|
251
233
|
|
|
252
234
|
const stateCount = buf.readUInt32LE(8)
|
|
@@ -257,7 +239,6 @@ export function deserializeFST(buf: Buffer): FSTMatcher {
|
|
|
257
239
|
|
|
258
240
|
let pos = HEADER_SIZE
|
|
259
241
|
|
|
260
|
-
// Verify string-table offsets and contents.
|
|
261
242
|
const strOffsets = new Uint32Array(stringCount + 1)
|
|
262
243
|
|
|
263
244
|
for (let i = 0; i <= stringCount; i++) {
|
|
@@ -276,9 +257,7 @@ export function deserializeFST(buf: Buffer): FSTMatcher {
|
|
|
276
257
|
|
|
277
258
|
pos += stringBytes
|
|
278
259
|
|
|
279
|
-
// Verify state-table offsets and transitions.
|
|
280
260
|
const stateEntrySize = version >= VERSION_WIDE_STATE_COUNTERS ? WIDE_STATE_ENTRY_SIZE : NARROW_STATE_ENTRY_SIZE
|
|
281
|
-
// v5 grew the place entry by the encyclopedic float; v4-and-below files are read at the old stride.
|
|
282
261
|
const placeEntrySize = version >= VERSION_TWO_SCORE_SPLIT ? SPLIT_PLACE_ENTRY_SIZE : LEGACY_PLACE_ENTRY_SIZE
|
|
283
262
|
const stateTableStart = pos
|
|
284
263
|
const edgeTableStart = stateTableStart + stateCount * stateEntrySize
|
|
@@ -317,16 +296,14 @@ export function deserializeFST(buf: Buffer): FSTMatcher {
|
|
|
317
296
|
parentChain.push(buf.readUInt32LE(pp + 24 + ci * 4))
|
|
318
297
|
}
|
|
319
298
|
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
// generation of this format for which that can be said without reading the source database.
|
|
299
|
+
// Version 1 stored a raw population u32 here.
|
|
300
|
+
// This version maps it through the `referentialFromPopulation` curve.
|
|
301
|
+
// Later versions store an f32 score.
|
|
324
302
|
const referential = isV2
|
|
325
303
|
? buf.readFloatLE(pp + 12)
|
|
326
304
|
: Math.min(1, Math.log2(1 + buf.readUInt32LE(pp + 12) / 1000) / 14)
|
|
327
305
|
|
|
328
|
-
//
|
|
329
|
-
// the field stays undefined rather than reading the reserved byte as a flag.
|
|
306
|
+
// Files before version 5 have no encyclopedic score, so their reserved byte is not read as a flag.
|
|
330
307
|
const hasEncyclopedic =
|
|
331
308
|
isSplit && (buf.readUInt8(pp + 7) & PLACE_FLAG_HAS_ENCYCLOPEDIC) === PLACE_FLAG_HAS_ENCYCLOPEDIC
|
|
332
309
|
|
|
@@ -338,9 +315,8 @@ export function deserializeFST(buf: Buffer): FSTMatcher {
|
|
|
338
315
|
lat: buf.readFloatLE(pp + 16),
|
|
339
316
|
lon: buf.readFloatLE(pp + 20),
|
|
340
317
|
parentChain,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
...(hasEncyclopedic ? { encyclopedic: buf.readFloatLE(pp + ENCYCLOPEDIC_OFFSET) } : {}),
|
|
318
|
+
crossCountryBranches: hasAmbiguity ? buf.readUInt8(pp + 6) : null,
|
|
319
|
+
encyclopedic: hasEncyclopedic ? buf.readFloatLE(pp + ENCYCLOPEDIC_OFFSET) : null,
|
|
344
320
|
}
|
|
345
321
|
}
|
|
346
322
|
|
|
@@ -350,23 +326,27 @@ export function deserializeFST(buf: Buffer): FSTMatcher {
|
|
|
350
326
|
return FSTMatcher.fromNodes(nodes)
|
|
351
327
|
}
|
|
352
328
|
|
|
353
|
-
|
|
354
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Reads the provenance trailer from an FST binary, or returns `undefined`
|
|
331
|
+
* when the file has none or it cannot be parsed.
|
|
332
|
+
*/
|
|
333
|
+
export function readFSTProvenance(buf: Buffer): FSTProvenance | null {
|
|
334
|
+
if (buf.length < HEADER_SIZE) return null
|
|
355
335
|
|
|
356
|
-
if (!buf.subarray(0, 4).equals(MAGIC)) return
|
|
336
|
+
if (!buf.subarray(0, 4).equals(MAGIC)) return null
|
|
357
337
|
const version = buf.readUInt16LE(4)
|
|
358
338
|
|
|
359
|
-
if (version < VERSION_WITH_METADATA) return
|
|
339
|
+
if (version < VERSION_WITH_METADATA) return null
|
|
360
340
|
const provenanceOffset = buf.readUInt32LE(28)
|
|
361
341
|
|
|
362
|
-
if (provenanceOffset === 0 || provenanceOffset >= buf.length) return
|
|
342
|
+
if (provenanceOffset === 0 || provenanceOffset >= buf.length) return null
|
|
363
343
|
|
|
364
344
|
try {
|
|
365
345
|
const jsonLen = buf.readUInt32LE(provenanceOffset)
|
|
366
346
|
const jsonStr = buf.toString("utf8", provenanceOffset + 4, provenanceOffset + 4 + jsonLen)
|
|
367
347
|
|
|
368
|
-
return tryParsingJSON<FSTProvenance>(jsonStr) ??
|
|
348
|
+
return tryParsingJSON<FSTProvenance>(jsonStr) ?? null
|
|
369
349
|
} catch {
|
|
370
|
-
return
|
|
350
|
+
return null
|
|
371
351
|
}
|
|
372
352
|
}
|