mailwoman 7.1.0 → 7.2.1
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/api-engine.ts +366 -0
- package/cli-kit/index.ts +125 -0
- package/cli-kit/weights-guard.tsx +221 -0
- package/cli.ts +27 -0
- package/commands/autocomplete.tsx +156 -0
- package/commands/clients/generate.tsx +75 -0
- package/commands/corpus/align-shard.tsx +42 -0
- package/commands/corpus/audit.tsx +43 -0
- package/commands/corpus/build.tsx +129 -0
- package/commands/corpus/download.tsx +126 -0
- package/commands/corpus/fetch.tsx +150 -0
- package/commands/corpus/golden/expand.tsx +66 -0
- package/commands/corpus/golden/promote.tsx +58 -0
- package/commands/corpus/ingest-csv.tsx +38 -0
- package/commands/corpus/list.tsx +36 -0
- package/commands/corpus/overlay-manifest.tsx +50 -0
- package/commands/corpus/run.tsx +134 -0
- package/commands/corpus/shard/index.tsx +137 -0
- package/commands/corpus/shard/kryptonite.tsx +48 -0
- package/commands/corpus/shard/translit.tsx +54 -0
- package/commands/corpus/stats.tsx +42 -0
- package/commands/corpus/upload.tsx +134 -0
- package/commands/coverage/build.tsx +131 -0
- package/commands/dev/download/libpostal-resources.tsx +32 -0
- package/commands/dev/download/ssl-address.tsx +41 -0
- package/commands/dev/generate/country-reference.tsx +34 -0
- package/commands/dev/generate/language-types.tsx +34 -0
- package/commands/dev/generate/official-languages.tsx +44 -0
- package/commands/dev/generate/trace-fixture.tsx +51 -0
- package/commands/dev/jsonl-to-parquet.tsx +46 -0
- package/commands/dev/lint/corpus-shard.tsx +53 -0
- package/commands/dev/lint/mdx-angles.tsx +53 -0
- package/commands/dev/lint/shard-vocab.tsx +57 -0
- package/commands/eval/capability-manifest.tsx +43 -0
- package/commands/eval/error-analysis.tsx +50 -0
- package/commands/eval/es-postcode-centroids.tsx +42 -0
- package/commands/eval/fragment-board.tsx +54 -0
- package/commands/eval/fragment-dev.tsx +44 -0
- package/commands/eval/gate.tsx +59 -0
- package/commands/eval/gauntlet-build.tsx +58 -0
- package/commands/eval/gauntlet.tsx +53 -0
- package/commands/eval/ledger-append.tsx +56 -0
- package/commands/eval/mask-regression.tsx +48 -0
- package/commands/eval/oa-resolver.tsx +85 -0
- package/commands/eval/oracle-k.tsx +62 -0
- package/commands/eval/parity.tsx +89 -0
- package/commands/eval/preset-compare.tsx +35 -0
- package/commands/gazetteer/affix-relabel.tsx +92 -0
- package/commands/gazetteer/anchor-lexicon.tsx +184 -0
- package/commands/gazetteer/build/admin.tsx +86 -0
- package/commands/gazetteer/build/anchor-lookup.tsx +38 -0
- package/commands/gazetteer/build/candidate.tsx +115 -0
- package/commands/gazetteer/build/coincident-roles.tsx +88 -0
- package/commands/gazetteer/build/fts.tsx +82 -0
- package/commands/gazetteer/build/index.tsx +82 -0
- package/commands/gazetteer/build/nl-pc6.tsx +38 -0
- package/commands/gazetteer/build/poi.tsx +104 -0
- package/commands/gazetteer/build/postcode-locality.tsx +113 -0
- package/commands/gazetteer/build/postcode-shard.tsx +73 -0
- package/commands/gazetteer/conventions.tsx +183 -0
- package/commands/gazetteer/importance.tsx +242 -0
- package/commands/gazetteer/inspect/fst.tsx +87 -0
- package/commands/gazetteer/inspect/graph.tsx +129 -0
- package/commands/gazetteer/inspect/mermaid.tsx +165 -0
- package/commands/gazetteer/inspect/placetype-stats.tsx +180 -0
- package/commands/gazetteer/inspect/sync.tsx +128 -0
- package/commands/gazetteer/inspect/tree.tsx +128 -0
- package/commands/gazetteer/overture-ingest.tsx +298 -0
- package/commands/gazetteer/polygons.tsx +295 -0
- package/commands/gazetteer/postal-alias.tsx +151 -0
- package/commands/gazetteer/postal-city.tsx +161 -0
- package/commands/gazetteer/postcode-binary.tsx +191 -0
- package/commands/gazetteer/postcode-intl.tsx +398 -0
- package/commands/gazetteer/promote.tsx +53 -0
- package/commands/gazetteer/publish.tsx +97 -0
- package/commands/gazetteer/release.tsx +132 -0
- package/commands/gazetteer/verify.tsx +61 -0
- package/commands/geocode.tsx +414 -0
- package/commands/gnaf/assemble.tsx +88 -0
- package/commands/openapi.tsx +44 -0
- package/commands/parse.tsx +718 -0
- package/commands/placer/build-dataset.tsx +95 -0
- package/commands/placer/eval.tsx +97 -0
- package/commands/placer/probe-frontier.tsx +48 -0
- package/commands/placer/quantize.tsx +45 -0
- package/commands/placer/train.tsx +54 -0
- package/commands/poi.tsx +277 -0
- package/commands/registry/convert/tx-hhsc.tsx +46 -0
- package/commands/registry/dedup-ceiling.tsx +51 -0
- package/commands/registry/gold-set-sample.tsx +59 -0
- package/commands/registry/matcher-scale.tsx +49 -0
- package/commands/registry/run.tsx +558 -0
- package/commands/registry/scorer-eval.tsx +267 -0
- package/commands/registry/train-scorer.tsx +108 -0
- package/commands/registry/viz.tsx +101 -0
- package/commands/release/hf.tsx +62 -0
- package/commands/reverse.tsx +169 -0
- package/commands/serve.tsx +240 -0
- package/commands/situs/address-points.tsx +341 -0
- package/commands/situs/attribution-manifest.tsx +135 -0
- package/commands/situs/build.tsx +321 -0
- package/commands/situs/interpolation-shard.tsx +324 -0
- package/commands/situs/interpolation.tsx +620 -0
- package/commands/tiger/fetch.tsx +104 -0
- package/commands/tiger/race-dots-map.tsx +107 -0
- package/commands/tiger/race-dots.tsx +65 -0
- package/commands/tiger/redistricting.tsx +99 -0
- package/commands/tiles/publish.tsx +91 -0
- package/commands/wof/graph.tsx +22 -0
- package/commands/wof/mermaid.tsx +22 -0
- package/commands/wof/prepare.tsx +22 -0
- package/commands/wof/sync.tsx +22 -0
- package/commands/wof/tree.tsx +22 -0
- package/coverage-core.ts +464 -0
- package/data-release.ts +63 -0
- package/default-placer.ts +59 -0
- package/default-street-evidence.ts +46 -0
- package/dev-tools/capture-v1-parse.run.ts +48 -0
- package/dev-tools/convert-parity-fixtures.run.ts +154 -0
- package/dev-tools/extract-parity-corpus.run.ts +42 -0
- package/dev-tools/failure-census.run.ts +0 -0
- package/dev-tools/failure-report.run.ts +0 -0
- package/dev-tools/generate-trace-fixture.ts +60 -0
- package/dev-tools/lint-mdx-angles.ts +114 -0
- package/dev-tools/parity-extract.ts +107 -0
- package/dev-tools/router-kind-probe.run.ts +63 -0
- package/dev-tools/us-recall-flip-census.run.ts +102 -0
- package/eval-harness/baseline-assert.ts +319 -0
- package/eval-harness/capability-manifest.ts +348 -0
- package/eval-harness/digit-board.ts +189 -0
- package/eval-harness/error-analysis.ts +306 -0
- package/eval-harness/es-postcode-centroids.ts +129 -0
- package/eval-harness/fragment-board.ts +183 -0
- package/eval-harness/fragment-dev.ts +113 -0
- package/eval-harness/gauntlet/build-fdic-holdout.ts +106 -0
- package/eval-harness/gauntlet/build-regression-db.ts +78 -0
- package/eval-harness/gauntlet/cases/regression.ts +667 -0
- package/eval-harness/gauntlet/harness.ts +163 -0
- package/eval-harness/gauntlet/holdout.ts +239 -0
- package/eval-harness/gauntlet/metamorphic.ts +496 -0
- package/eval-harness/gauntlet/regression.ts +164 -0
- package/eval-harness/gauntlet/run.ts +129 -0
- package/eval-harness/gauntlet/schema.ts +115 -0
- package/eval-harness/ledger-append.ts +208 -0
- package/eval-harness/mask-regression.ts +334 -0
- package/eval-harness/oa-resolver-eval.ts +1255 -0
- package/eval-harness/oracle-k.ts +415 -0
- package/eval-harness/parity-corpus.ts +278 -0
- package/eval-harness/preset-compare.ts +62 -0
- package/eval-harness/promotion-gate-verdict.ts +261 -0
- package/eval-harness/promotion-gate.ts +531 -0
- package/eval-harness/v0-tree-adapter.ts +113 -0
- package/gazetteer-pipeline/admin/enrich.ts +99 -0
- package/gazetteer-pipeline/admin/fold-geonames.ts +64 -0
- package/gazetteer-pipeline/admin/fold-overture.ts +179 -0
- package/gazetteer-pipeline/admin/freeze.ts +112 -0
- package/gazetteer-pipeline/admin/index.ts +230 -0
- package/gazetteer-pipeline/admin/ingest-wof.ts +266 -0
- package/gazetteer-pipeline/anchor-lookup.ts +371 -0
- package/gazetteer-pipeline/defaults.ts +305 -0
- package/gazetteer-pipeline/fts.ts +35 -0
- package/gazetteer-pipeline/index.ts +321 -0
- package/gazetteer-pipeline/poi/build-poi.ts +538 -0
- package/gazetteer-pipeline/postcode/centroid-fills.ts +244 -0
- package/gazetteer-pipeline/postcode/index.ts +164 -0
- package/gazetteer-pipeline/postcode/nl-pc6.ts +120 -0
- package/gazetteer-pipeline/postcode/zcta-centroids.ts +203 -0
- package/gazetteer-pipeline/postcode-locality/base.ts +463 -0
- package/gazetteer-pipeline/postcode-locality/jp.ts +362 -0
- package/gazetteer-pipeline/postcode-locality/kr.ts +408 -0
- package/gazetteer-pipeline/postcode-locality/tw.ts +634 -0
- package/gazetteer-pipeline/verify-baseline.ts +222 -0
- package/gazetteer-pipeline/verify.ts +233 -0
- package/geocode-core.ts +877 -0
- package/geocode-stream.ts +85 -0
- package/geocode-worker.ts +54 -0
- package/index.ts +9 -0
- package/interp-calibration.ts +65 -0
- package/kbest-street-rerank.ts +248 -0
- package/out/api-engine.d.ts +44 -0
- package/out/cli-kit/index.d.ts +71 -0
- package/out/cli-kit/weights-guard.d.ts +64 -0
- package/out/cli.d.ts +8 -0
- package/out/commands/autocomplete.d.ts +45 -0
- package/out/commands/clients/generate.d.ts +26 -0
- package/out/commands/corpus/align-shard.d.ts +19 -0
- package/out/commands/corpus/audit.d.ts +19 -0
- package/out/commands/corpus/build.d.ts +28 -0
- package/out/commands/corpus/download.d.ts +24 -0
- package/out/commands/corpus/fetch.d.ts +45 -0
- package/out/commands/corpus/golden/expand.d.ts +29 -0
- package/out/commands/corpus/golden/promote.d.ts +24 -0
- package/out/commands/corpus/ingest-csv.d.ts +25 -0
- package/out/commands/corpus/list.d.ts +18 -0
- package/out/commands/corpus/overlay-manifest.d.ts +23 -0
- package/out/commands/corpus/run.d.ts +26 -0
- package/out/commands/corpus/shard/index.d.ts +43 -0
- package/out/commands/corpus/shard/kryptonite.d.ts +22 -0
- package/out/commands/corpus/shard/translit.d.ts +24 -0
- package/out/commands/corpus/stats.d.ts +19 -0
- package/out/commands/corpus/upload.d.ts +24 -0
- package/out/commands/coverage/build.d.ts +41 -0
- package/out/commands/dev/download/libpostal-resources.d.ts +17 -0
- package/out/commands/dev/download/ssl-address.d.ts +18 -0
- package/out/commands/dev/generate/country-reference.d.ts +12 -0
- package/out/commands/dev/generate/language-types.d.ts +12 -0
- package/out/commands/dev/generate/official-languages.d.ts +19 -0
- package/out/commands/dev/generate/trace-fixture.d.ts +18 -0
- package/out/commands/dev/jsonl-to-parquet.d.ts +21 -0
- package/out/commands/dev/lint/corpus-shard.d.ts +23 -0
- package/out/commands/dev/lint/mdx-angles.d.ts +17 -0
- package/out/commands/dev/lint/shard-vocab.d.ts +25 -0
- package/out/commands/eval/capability-manifest.d.ts +25 -0
- package/out/commands/eval/error-analysis.d.ts +26 -0
- package/out/commands/eval/es-postcode-centroids.d.ts +24 -0
- package/out/commands/eval/fragment-board.d.ts +25 -0
- package/out/commands/eval/fragment-dev.d.ts +21 -0
- package/out/commands/eval/gate.d.ts +30 -0
- package/out/commands/eval/gauntlet-build.d.ts +24 -0
- package/out/commands/eval/gauntlet.d.ts +31 -0
- package/out/commands/eval/ledger-append.d.ts +29 -0
- package/out/commands/eval/mask-regression.d.ts +27 -0
- package/out/commands/eval/oa-resolver.d.ts +54 -0
- package/out/commands/eval/oracle-k.d.ts +25 -0
- package/out/commands/eval/parity.d.ts +29 -0
- package/out/commands/eval/preset-compare.d.ts +20 -0
- package/out/commands/gazetteer/affix-relabel.d.ts +22 -0
- package/out/commands/gazetteer/anchor-lexicon.d.ts +35 -0
- package/out/commands/gazetteer/build/admin.d.ts +25 -0
- package/out/commands/gazetteer/build/anchor-lookup.d.ts +19 -0
- package/out/commands/gazetteer/build/candidate.d.ts +23 -0
- package/out/commands/gazetteer/build/coincident-roles.d.ts +21 -0
- package/out/commands/gazetteer/build/fts.d.ts +20 -0
- package/out/commands/gazetteer/build/index.d.ts +21 -0
- package/out/commands/gazetteer/build/nl-pc6.d.ts +19 -0
- package/out/commands/gazetteer/build/poi.d.ts +24 -0
- package/out/commands/gazetteer/build/poi.d.ts.map +1 -0
- package/out/commands/gazetteer/build/poi.js +83 -0
- package/out/commands/gazetteer/build/poi.js.map +1 -0
- package/out/commands/gazetteer/build/postcode-locality.d.ts +44 -0
- package/out/commands/gazetteer/build/postcode-shard.d.ts +25 -0
- package/out/commands/gazetteer/conventions.d.ts +33 -0
- package/out/commands/gazetteer/importance.d.ts +26 -0
- package/out/commands/gazetteer/importance.d.ts.map +1 -1
- package/out/commands/gazetteer/importance.js +2 -1
- package/out/commands/gazetteer/importance.js.map +1 -1
- package/out/commands/gazetteer/inspect/fst.d.ts +23 -0
- package/out/commands/gazetteer/inspect/graph.d.ts +25 -0
- package/out/commands/gazetteer/inspect/mermaid.d.ts +24 -0
- package/out/commands/gazetteer/inspect/placetype-stats.d.ts +25 -0
- package/out/commands/gazetteer/inspect/placetype-stats.d.ts.map +1 -1
- package/out/commands/gazetteer/inspect/placetype-stats.js.map +1 -1
- package/out/commands/gazetteer/inspect/sync.d.ts +23 -0
- package/out/commands/gazetteer/inspect/tree.d.ts +24 -0
- package/out/commands/gazetteer/overture-ingest.d.ts +43 -0
- package/out/commands/gazetteer/polygons.d.ts +42 -0
- package/out/commands/gazetteer/postal-alias.d.ts +37 -0
- package/out/commands/gazetteer/postal-city.d.ts +32 -0
- package/out/commands/gazetteer/postcode-binary.d.ts +34 -0
- package/out/commands/gazetteer/postcode-intl.d.ts +51 -0
- package/out/commands/gazetteer/promote.d.ts +18 -0
- package/out/commands/gazetteer/publish.d.ts +27 -0
- package/out/commands/gazetteer/release.d.ts +27 -0
- package/out/commands/gazetteer/verify.d.ts +21 -0
- package/out/commands/geocode.d.ts +52 -0
- package/out/commands/gnaf/assemble.d.ts +29 -0
- package/out/commands/openapi.d.ts +27 -0
- package/out/commands/parse.d.ts +57 -0
- package/out/commands/parse.d.ts.map +1 -1
- package/out/commands/parse.js +7 -5
- package/out/commands/parse.js.map +1 -1
- package/out/commands/placer/build-dataset.d.ts +31 -0
- package/out/commands/placer/eval.d.ts +33 -0
- package/out/commands/placer/probe-frontier.d.ts +21 -0
- package/out/commands/placer/quantize.d.ts +19 -0
- package/out/commands/placer/train.d.ts +23 -0
- package/out/commands/poi.d.ts +41 -0
- package/out/commands/poi.d.ts.map +1 -0
- package/out/commands/poi.js +219 -0
- package/out/commands/poi.js.map +1 -0
- package/out/commands/registry/convert/tx-hhsc.d.ts +20 -0
- package/out/commands/registry/dedup-ceiling.d.ts +22 -0
- package/out/commands/registry/gold-set-sample.d.ts +23 -0
- package/out/commands/registry/matcher-scale.d.ts +21 -0
- package/out/commands/registry/run.d.ts +97 -0
- package/out/commands/registry/scorer-eval.d.ts +60 -0
- package/out/commands/registry/train-scorer.d.ts +34 -0
- package/out/commands/registry/viz.d.ts +35 -0
- package/out/commands/release/hf.d.ts +34 -0
- package/out/commands/reverse.d.ts +32 -0
- package/out/commands/serve.d.ts +19 -0
- package/out/commands/situs/address-points.d.ts +44 -0
- package/out/commands/situs/attribution-manifest.d.ts +27 -0
- package/out/commands/situs/build.d.ts +49 -0
- package/out/commands/situs/interpolation-shard.d.ts +37 -0
- package/out/commands/situs/interpolation.d.ts +43 -0
- package/out/commands/tiger/fetch.d.ts +29 -0
- package/out/commands/tiger/race-dots-map.d.ts +27 -0
- package/out/commands/tiger/race-dots.d.ts +21 -0
- package/out/commands/tiger/redistricting.d.ts +24 -0
- package/out/commands/tiles/publish.d.ts +38 -0
- package/out/commands/wof/graph.d.ts +10 -0
- package/out/commands/wof/mermaid.d.ts +10 -0
- package/out/commands/wof/prepare.d.ts +10 -0
- package/out/commands/wof/sync.d.ts +10 -0
- package/out/commands/wof/tree.d.ts +10 -0
- package/out/coverage-core.d.ts +99 -0
- package/out/data-release.d.ts +26 -0
- package/out/default-placer.d.ts +31 -0
- package/out/default-street-evidence.d.ts +24 -0
- package/out/dev-tools/capture-v1-parse.run.d.ts +13 -0
- package/out/dev-tools/convert-parity-fixtures.run.d.ts +30 -0
- package/out/dev-tools/extract-parity-corpus.run.d.ts +11 -0
- package/out/dev-tools/failure-census.run.d.ts +2 -0
- package/out/dev-tools/failure-report.run.d.ts +23 -0
- package/out/dev-tools/generate-trace-fixture.d.ts +32 -0
- package/out/dev-tools/lint-mdx-angles.d.ts +37 -0
- package/out/dev-tools/parity-extract.d.ts +23 -0
- package/out/dev-tools/router-kind-probe.run.d.ts +2 -0
- package/out/dev-tools/us-recall-flip-census.run.d.ts +12 -0
- package/out/eval-harness/baseline-assert.d.ts +139 -0
- package/out/eval-harness/capability-manifest.d.ts +57 -0
- package/out/eval-harness/digit-board.d.ts +64 -0
- package/out/eval-harness/error-analysis.d.ts +57 -0
- package/out/eval-harness/es-postcode-centroids.d.ts +35 -0
- package/out/eval-harness/fragment-board.d.ts +63 -0
- package/out/eval-harness/fragment-dev.d.ts +30 -0
- package/out/eval-harness/gauntlet/build-fdic-holdout.d.ts +19 -0
- package/out/eval-harness/gauntlet/build-regression-db.d.ts +13 -0
- package/out/eval-harness/gauntlet/cases/regression.d.ts +33 -0
- package/out/eval-harness/gauntlet/harness.d.ts +44 -0
- package/out/eval-harness/gauntlet/holdout.d.ts +46 -0
- package/out/eval-harness/gauntlet/metamorphic.d.ts +32 -0
- package/out/eval-harness/gauntlet/regression.d.ts +39 -0
- package/out/eval-harness/gauntlet/run.d.ts +57 -0
- package/out/eval-harness/gauntlet/schema.d.ts +69 -0
- package/out/eval-harness/ledger-append.d.ts +59 -0
- package/out/eval-harness/mask-regression.d.ts +85 -0
- package/out/eval-harness/oa-resolver-eval.d.ts +132 -0
- package/out/eval-harness/oracle-k.d.ts +76 -0
- package/out/eval-harness/parity-corpus.d.ts +71 -0
- package/out/eval-harness/preset-compare.d.ts +19 -0
- package/out/eval-harness/promotion-gate-verdict.d.ts +65 -0
- package/out/eval-harness/promotion-gate.d.ts +91 -0
- package/out/eval-harness/v0-tree-adapter.d.ts +44 -0
- package/out/gazetteer-pipeline/admin/enrich.d.ts +30 -0
- package/out/gazetteer-pipeline/admin/fold-geonames.d.ts +37 -0
- package/out/gazetteer-pipeline/admin/fold-overture.d.ts +42 -0
- package/out/gazetteer-pipeline/admin/freeze.d.ts +29 -0
- package/out/gazetteer-pipeline/admin/index.d.ts +52 -0
- package/out/gazetteer-pipeline/admin/ingest-wof.d.ts +38 -0
- package/out/gazetteer-pipeline/anchor-lookup.d.ts +48 -0
- package/out/gazetteer-pipeline/defaults.d.ts +35 -0
- package/out/gazetteer-pipeline/fts.d.ts +23 -0
- package/out/gazetteer-pipeline/index.d.ts +139 -0
- package/out/gazetteer-pipeline/poi/build-poi.d.ts +159 -0
- package/out/gazetteer-pipeline/poi/build-poi.d.ts.map +1 -0
- package/out/gazetteer-pipeline/poi/build-poi.js +352 -0
- package/out/gazetteer-pipeline/poi/build-poi.js.map +1 -0
- package/out/gazetteer-pipeline/postcode/centroid-fills.d.ts +42 -0
- package/out/gazetteer-pipeline/postcode/index.d.ts +41 -0
- package/out/gazetteer-pipeline/postcode/nl-pc6.d.ts +42 -0
- package/out/gazetteer-pipeline/postcode/zcta-centroids.d.ts +73 -0
- package/out/gazetteer-pipeline/postcode-locality/base.d.ts +53 -0
- package/out/gazetteer-pipeline/postcode-locality/jp.d.ts +46 -0
- package/out/gazetteer-pipeline/postcode-locality/kr.d.ts +46 -0
- package/out/gazetteer-pipeline/postcode-locality/tw.d.ts +100 -0
- package/out/gazetteer-pipeline/verify-baseline.d.ts +17 -0
- package/out/gazetteer-pipeline/verify.d.ts +56 -0
- package/out/geocode-core.d.ts +282 -0
- package/out/geocode-stream.d.ts +63 -0
- package/out/geocode-worker.d.ts +12 -0
- package/out/index.d.ts +9 -0
- package/out/interp-calibration.d.ts +40 -0
- package/out/kbest-street-rerank.d.ts +73 -0
- package/out/poi-executor.d.ts +42 -0
- package/out/poi-executor.d.ts.map +1 -0
- package/out/poi-executor.js +95 -0
- package/out/poi-executor.js.map +1 -0
- package/out/poi-intent.d.ts +31 -0
- package/out/poi-intent.d.ts.map +1 -0
- package/out/poi-intent.js +39 -0
- package/out/poi-intent.js.map +1 -0
- package/out/poi-overpass.d.ts +11 -0
- package/out/poi-overpass.d.ts.map +1 -0
- package/out/poi-overpass.js +11 -0
- package/out/poi-overpass.js.map +1 -0
- package/out/region-recognition.d.ts +38 -0
- package/out/release-tools/publish-hf.d.ts +53 -0
- package/out/resolver-backend.d.ts +52 -0
- package/out/runtime-pipeline.d.ts +124 -0
- package/out/runtime-pipeline.d.ts.map +1 -1
- package/out/runtime-pipeline.js +55 -2
- package/out/runtime-pipeline.js.map +1 -1
- package/out/test-fixtures/fake-geocode-worker.d.ts +11 -0
- package/out/test-kit/index.d.ts +24 -0
- package/out/tools/generate-clients.d.ts +80 -0
- package/package.json +42 -25
- package/poi-executor.ts +133 -0
- package/poi-intent.ts +70 -0
- package/poi-overpass.ts +11 -0
- package/region-recognition.ts +234 -0
- package/release-tools/publish-hf.ts +328 -0
- package/resolver-backend.ts +95 -0
- package/runtime-pipeline.ts +331 -0
- package/test-kit/index.ts +78 -0
- package/tools/generate-clients.ts +909 -0
- package/types/node.d.ts +45 -0
package/api-engine.ts
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The wired {@link MailwomanAPIEngine} for `mailwoman serve` (Phase 4b, the Hono cutover). Ports the
|
|
7
|
+
* former express `server/`'s handler bodies — `GeocodeRouter.getDeps` + its retired `/api/geocode`,
|
|
8
|
+
* `/api/batch`, `/api/resolve-tree`, `/api/reload` handlers, `AddressRouter`'s retired `/parse`
|
|
9
|
+
* handler, and `HealthRouter`'s `/health` data block — onto the engine-agnostic `@mailwoman/api`
|
|
10
|
+
* contract's live routes (`/v1/parse`, `/v1/geocode`, `/v1/batch`, `/v1/resolve`, `/v1/reload`).
|
|
11
|
+
* `mailwoman/server/` is deleted (Task 2) — this file is its sole successor, a fresh port rather
|
|
12
|
+
* than a thin wrapper.
|
|
13
|
+
*
|
|
14
|
+
* `createServeEngine` builds the shared stack ONCE, at boot, instead of express's lazy
|
|
15
|
+
* first-request memoized promise — the CLI's `serve` command awaits it before listening, so a
|
|
16
|
+
* misconfigured deployment fails FRIENDLY at boot (the #1009 pattern the drop-ins already use)
|
|
17
|
+
* instead of a runtime 503 on the first request. `parse` speaks native neural output (`ParseOutcome`
|
|
18
|
+
* = ordered components + the decoded `AddressTree`, the same language `/v1/resolve` speaks) — it
|
|
19
|
+
* needs only the model weights, loaded ONCE here and reused by the geocode stack below, so it is
|
|
20
|
+
* built independently of the WOF-data gate: a WOF-less boot still answers `/v1/parse`, while
|
|
21
|
+
* `geocode`/`batch`/`resolveTree`/`reload` are simply absent (`@mailwoman/api`'s routes answer 503
|
|
22
|
+
* for those on their own). When the weights themselves are unresolvable (`@mailwoman/neural`
|
|
23
|
+
* missing, or no weights package installed), `parse` is ALSO absent and the routes answer 501 — no
|
|
24
|
+
* rules fallback (the legacy-excision's point). `health` always answers, even when everything else
|
|
25
|
+
* is broken.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs"
|
|
29
|
+
import { createRequire } from "node:module"
|
|
30
|
+
import { join } from "node:path"
|
|
31
|
+
|
|
32
|
+
import type { BatchRow, GeocodeOutcome, HealthData, MailwomanAPIEngine, ResolveTreeOutcome } from "@mailwoman/api"
|
|
33
|
+
import { recordTimed } from "@mailwoman/api-kit"
|
|
34
|
+
import { decodeAsTuples, decodeAsXML } from "@mailwoman/core"
|
|
35
|
+
import type { AddressTree } from "@mailwoman/core/decoder"
|
|
36
|
+
import { $public } from "@mailwoman/core/env"
|
|
37
|
+
import { createWOFResolver, type Resolver, type ResolveOpts } from "@mailwoman/resolver"
|
|
38
|
+
|
|
39
|
+
import { readReleaseManifest } from "./data-release.ts"
|
|
40
|
+
import {
|
|
41
|
+
geocodeAddress,
|
|
42
|
+
type GeocodeClassifier,
|
|
43
|
+
type GeocodeResult,
|
|
44
|
+
regionSlugFromTree,
|
|
45
|
+
ShardProvider,
|
|
46
|
+
} from "./geocode-core.ts"
|
|
47
|
+
import { INTERP_RADIUS_CALIBRATION, interpCalibrationForRegion } from "./interp-calibration.ts"
|
|
48
|
+
import { createResolverBackend, mailwomanDataRoot, resolveCandidateDBPath, wofShardPaths } from "./resolver-backend.ts"
|
|
49
|
+
|
|
50
|
+
/** Default per-state shard root + interp calibration — mirrors the express server's defaults (`GeocodeRouter.ts`). */
|
|
51
|
+
const DATA_ROOT = mailwomanDataRoot()
|
|
52
|
+
|
|
53
|
+
/** The classifier/resolver/shard bundle `geocode`/`batch`/`resolveTree`/`reload` close over. */
|
|
54
|
+
interface GeocodeDepsBundle {
|
|
55
|
+
classifier: GeocodeClassifier
|
|
56
|
+
resolver: Resolver
|
|
57
|
+
shards: ShardProvider
|
|
58
|
+
defaultCountry?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Same WOF-path resolution as the express `GeocodeRouter`/`HealthRouter` (env override, else the conventional shards). */
|
|
62
|
+
function wofPaths(): string[] {
|
|
63
|
+
const env = $public.MAILWOMAN_WOF_DB
|
|
64
|
+
|
|
65
|
+
if (env)
|
|
66
|
+
return env
|
|
67
|
+
.split(",")
|
|
68
|
+
.map((p) => p.trim())
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
|
|
71
|
+
return wofShardPaths().filter((p) => existsSync(p))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* #1009-style boot preflight message. Same shape as the drop-ins' (`photon/cli.ts`, `nominatim/cli.ts`) — a stranger's
|
|
76
|
+
* first `mailwoman serve` must say exactly what data is missing and the one command that fixes it — adapted to this
|
|
77
|
+
* package's own override (`MAILWOMAN_WOF_DB`, a comma-separated FTS shard list) alongside the shared
|
|
78
|
+
* candidate-gazetteer fast path (`MAILWOMAN_CANDIDATE_DB`).
|
|
79
|
+
*/
|
|
80
|
+
function buildPreflightMessage(): string {
|
|
81
|
+
const conventionCandidate = join(DATA_ROOT, "wof", "candidate.db")
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
"✗ no gazetteer data found — `mailwoman serve` needs a WOF SQLite distribution to geocode/resolve.",
|
|
85
|
+
"",
|
|
86
|
+
" Fastest path (worldwide resolution, ~1.4 GB, byte-range friendly):",
|
|
87
|
+
` mkdir -p ${join(DATA_ROOT, "wof")}`,
|
|
88
|
+
` curl -fSL https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db \\`,
|
|
89
|
+
` -o ${conventionCandidate}`,
|
|
90
|
+
"",
|
|
91
|
+
" Then re-run `serve` (the file is auto-detected at that path), or point at your own:",
|
|
92
|
+
" $MAILWOMAN_WOF_DB=<path1,path2,...> (admin WOF SQLite shard(s) — the FTS backend)",
|
|
93
|
+
" $MAILWOMAN_CANDIDATE_DB=<path> (candidate-table gazetteer — population-first, demo-parity)",
|
|
94
|
+
"",
|
|
95
|
+
" Docs: https://mailwoman.sister.software/docs",
|
|
96
|
+
].join("\n")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Best-effort model-card read: env override → installed weights package → dev-tree fallback. Ported from
|
|
101
|
+
* `HealthRouter`.
|
|
102
|
+
*/
|
|
103
|
+
function readModelCard(): Record<string, unknown> | null {
|
|
104
|
+
const candidates: string[] = []
|
|
105
|
+
|
|
106
|
+
if ($public.MAILWOMAN_MODEL_CARD) {
|
|
107
|
+
candidates.push($public.MAILWOMAN_MODEL_CARD)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
candidates.push(createRequire(import.meta.url).resolve("@mailwoman/neural-weights-en-us/model-card.json"))
|
|
112
|
+
} catch {
|
|
113
|
+
/* package not resolvable from here — fall through */
|
|
114
|
+
}
|
|
115
|
+
candidates.push("neural-weights-en-us/model-card.json")
|
|
116
|
+
|
|
117
|
+
for (const p of candidates) {
|
|
118
|
+
try {
|
|
119
|
+
if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8")) as Record<string, unknown>
|
|
120
|
+
} catch {
|
|
121
|
+
/* unreadable / malformed — try the next candidate */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Count canonical per-state shards (`<prefix>-us-<2-letter>.db`) in a data subdir; 0 if absent. Ported from
|
|
130
|
+
* `HealthRouter`.
|
|
131
|
+
*/
|
|
132
|
+
function countShards(subdir: string, prefix: string): number {
|
|
133
|
+
try {
|
|
134
|
+
const re = new RegExp(`^${prefix}-us-[a-z]{2}\\.db$`)
|
|
135
|
+
|
|
136
|
+
return readdirSync(`${DATA_ROOT}/${subdir}`).filter((f) => re.test(f)).length
|
|
137
|
+
} catch {
|
|
138
|
+
return 0
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The `/health` data block: model card + data-root inventory. Ported from `HealthRouter`'s `healthHandler`. Always
|
|
144
|
+
* available — reads files best-effort and never throws, regardless of preflight status.
|
|
145
|
+
*/
|
|
146
|
+
function buildHealthData(): HealthData {
|
|
147
|
+
const card = readModelCard()
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
model: card
|
|
151
|
+
? {
|
|
152
|
+
name: card["name"],
|
|
153
|
+
version: card["version"],
|
|
154
|
+
locale: card["locale"],
|
|
155
|
+
labels: Array.isArray(card["labels"]) ? card["labels"].length : undefined,
|
|
156
|
+
format: card["format"],
|
|
157
|
+
}
|
|
158
|
+
: null,
|
|
159
|
+
data: {
|
|
160
|
+
data_root: DATA_ROOT,
|
|
161
|
+
// Versioned-switchover provenance (#485): the releases.json pin, or null in legacy mode.
|
|
162
|
+
versions: readReleaseManifest(DATA_ROOT),
|
|
163
|
+
// The express HealthRouter existsSync-filtered here where GeocodeRouter's wofPaths() didn't;
|
|
164
|
+
// this diagnostic field keeps the health-side behavior (no phantom env paths in "what's deployed").
|
|
165
|
+
wof_dbs: wofPaths().filter((p) => existsSync(p)),
|
|
166
|
+
situs_states: countShards("address-points", "address-points"),
|
|
167
|
+
interpolation_states: countShards("interpolation", "interpolation"),
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** One geocode call over the shared deps. Ported from `GeocodeRouter`'s `oneGeocode`. */
|
|
173
|
+
function oneGeocode(deps: GeocodeDepsBundle, address: string): Promise<GeocodeResult> {
|
|
174
|
+
return geocodeAddress(address, {
|
|
175
|
+
classifier: deps.classifier,
|
|
176
|
+
resolver: deps.resolver,
|
|
177
|
+
shards: deps.shards.for,
|
|
178
|
+
defaultCountry: deps.defaultCountry,
|
|
179
|
+
interpCalibration: INTERP_RADIUS_CALIBRATION,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Pull the street node's resolution tier (if any) for the metric. Ported verbatim from `GeocodeRouter`. */
|
|
184
|
+
function collectStreetTier(
|
|
185
|
+
node: AddressTree["roots"][number]
|
|
186
|
+
): Array<"address_point" | "interpolated" | "street" | "admin"> {
|
|
187
|
+
const out: Array<"address_point" | "interpolated" | "street" | "admin"> = []
|
|
188
|
+
|
|
189
|
+
if (node.tag === "street") {
|
|
190
|
+
const tier = node.metadata?.["resolution_tier"]
|
|
191
|
+
|
|
192
|
+
if (tier === "address_point" || tier === "interpolated" || tier === "street") {
|
|
193
|
+
out.push(tier)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const child of node.children) {
|
|
198
|
+
out.push(...collectStreetTier(child))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return out
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** {@link createServeEngine}'s return value. */
|
|
205
|
+
export interface ServeEngine {
|
|
206
|
+
engine: MailwomanAPIEngine
|
|
207
|
+
preflight: { ok: true } | { ok: false; message: string }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Build the wired `mailwoman serve` engine. Awaited ONCE at boot (unlike express's lazy per-request `getDeps()`), so a
|
|
212
|
+
* misconfigured deployment reports its preflight failure before the process starts listening — the caller (the `serve`
|
|
213
|
+
* command) decides whether to boot degraded (parse+health only) or exit friendly.
|
|
214
|
+
*/
|
|
215
|
+
export async function createServeEngine(): Promise<ServeEngine> {
|
|
216
|
+
// `health` reads files best-effort and never throws — wired unconditionally, matching `HealthRouter`'s "answers even
|
|
217
|
+
// when broken" contract.
|
|
218
|
+
const health: MailwomanAPIEngine["health"] = () => buildHealthData()
|
|
219
|
+
|
|
220
|
+
// Parse needs only the model weights — not the gazetteer. Load them independently of the WOF-data gate below so
|
|
221
|
+
// `/v1/parse` answers whenever weights resolve, even on a geocode-degraded boot. The classifier instance loaded
|
|
222
|
+
// here is reused by the geocode stack below — weights load ONCE per boot.
|
|
223
|
+
let parse: MailwomanAPIEngine["parse"]
|
|
224
|
+
let neuralMod: typeof import("@mailwoman/neural") | undefined
|
|
225
|
+
let classifier: GeocodeClassifier | undefined
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
neuralMod = await import("@mailwoman/neural")
|
|
229
|
+
classifier = await neuralMod.NeuralAddressClassifier.loadFromWeights({ locale: "en-US" })
|
|
230
|
+
|
|
231
|
+
const parseClassifier = classifier
|
|
232
|
+
parse = async (address, opts) => {
|
|
233
|
+
const tree = await parseClassifier.parse(address, { postcodeRepair: true })
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
input: address,
|
|
237
|
+
components: decodeAsTuples(tree).map(([tag, value]) => ({ tag, value })),
|
|
238
|
+
tree,
|
|
239
|
+
debug: opts.debug ? decodeAsXML(tree) : undefined,
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
// Weights unresolvable — leave parse undefined; the route answers 501 with its existing guard.
|
|
244
|
+
console.error("createServeEngine: neural weights not found — /v1/parse disabled (501)")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!neuralMod || !classifier) {
|
|
248
|
+
console.error("createServeEngine: @mailwoman/neural + @mailwoman/resolver-wof-sqlite are required")
|
|
249
|
+
|
|
250
|
+
return { engine: { parse, health }, preflight: { ok: false, message: buildPreflightMessage() } }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let resolverMod: typeof import("@mailwoman/resolver-wof-sqlite")
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
resolverMod = await import("@mailwoman/resolver-wof-sqlite")
|
|
257
|
+
} catch {
|
|
258
|
+
console.error("createServeEngine: @mailwoman/neural + @mailwoman/resolver-wof-sqlite are required")
|
|
259
|
+
|
|
260
|
+
return { engine: { parse, health }, preflight: { ok: false, message: buildPreflightMessage() } }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const paths = wofPaths()
|
|
264
|
+
// Candidate backend → country-agnostic default (demo's global, population-first behavior); a per-request `country`
|
|
265
|
+
// still scopes. FTS backend keeps the US default. (#170) A candidate DB alone (no WOF admin shard) is a valid boot
|
|
266
|
+
// configuration — `createResolverBackend` prefers it over `wofPaths` — so the preflight gate below checks BOTH,
|
|
267
|
+
// mirroring the drop-ins' `!candidateDb && wofPaths.length === 0` gate rather than `GeocodeRouter`'s WOF-only check.
|
|
268
|
+
// This gate governs geocode/batch/resolveTree/reload ONLY — `parse` is already wired above and unaffected.
|
|
269
|
+
const candidateDb = resolveCandidateDBPath()
|
|
270
|
+
|
|
271
|
+
if (paths.length === 0 && !candidateDb) {
|
|
272
|
+
console.error("createServeEngine: no WOF DBs found — set MAILWOMAN_WOF_DB or MAILWOMAN_CANDIDATE_DB")
|
|
273
|
+
|
|
274
|
+
return { engine: { parse, health }, preflight: { ok: false, message: buildPreflightMessage() } }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const backend = createResolverBackend(resolverMod, { wofPaths: paths })
|
|
278
|
+
const resolver = createWOFResolver(backend)
|
|
279
|
+
const shards = new ShardProvider(resolverMod, DATA_ROOT)
|
|
280
|
+
const deps: GeocodeDepsBundle = { classifier, resolver, shards, defaultCountry: candidateDb ? undefined : "US" }
|
|
281
|
+
|
|
282
|
+
// Route records the whole-call metric already (`@mailwoman/api`'s `routes.ts`) — the engine records nothing extra
|
|
283
|
+
// here. Ported from `GeocodeRouter`'s `singleHandler`. The cast mirrors `@mailwoman/api/routes.ts`'s established
|
|
284
|
+
// "documented wire shape looser than the domain type" idiom — `GeocodeOutcome` is a deliberately loose passthrough.
|
|
285
|
+
const geocode: MailwomanAPIEngine["geocode"] = async (address) =>
|
|
286
|
+
(await oneGeocode(deps, address)) as unknown as GeocodeOutcome
|
|
287
|
+
|
|
288
|
+
// Sequential loop — results land in input order; a thrown row is isolated to its own
|
|
289
|
+
// `{ input, error }` slot. Rows are trimmed here (the route passes the raw validated array through).
|
|
290
|
+
//
|
|
291
|
+
// This was a bounded-concurrency worker pool (`MAILWOMAN_BATCH_CONCURRENCY`, default 8) until
|
|
292
|
+
// 2026-07-16. The pool was measured at 1.00x — a geocode cannot overlap another in-process, because
|
|
293
|
+
// `onnxruntime-node`'s `session.run()` blocks the JS thread rather than releasing to the libuv pool,
|
|
294
|
+
// and `node:sqlite` reads are synchronous. The pool bought nothing but the appearance of tuning, so
|
|
295
|
+
// it's a plain loop now. To actually parallelize, cross a thread boundary — see
|
|
296
|
+
// `mailwoman/geocode-stream.ts`. Receipts: `docs/articles/plan/reference/performance.mdx`.
|
|
297
|
+
const batch: MailwomanAPIEngine["batch"] = async (addresses) => {
|
|
298
|
+
const inputs = addresses.map((a) => a.trim())
|
|
299
|
+
const results: BatchRow[] = new Array<BatchRow>(inputs.length)
|
|
300
|
+
|
|
301
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
302
|
+
const input = inputs[i]!
|
|
303
|
+
const t0 = performance.now()
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const result = await oneGeocode(deps, input)
|
|
307
|
+
recordTimed(performance.now() - t0, result.resolution_tier)
|
|
308
|
+
results[i] = result as unknown as GeocodeOutcome
|
|
309
|
+
} catch (err) {
|
|
310
|
+
recordTimed(performance.now() - t0, "error")
|
|
311
|
+
results[i] = { input, error: err instanceof Error ? err.message : String(err) }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return { results }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Metrics are the engine's own responsibility here — unlike `/v1/geocode`, the route wraps no try/catch around
|
|
319
|
+
// `resolveTree` (it lets a fault fall through to the app's 500 safety net), so the tier metric AND the rethrow both
|
|
320
|
+
// happen here. Ported from `GeocodeRouter`'s `resolveTreeHandler`.
|
|
321
|
+
const resolveTree: MailwomanAPIEngine["resolveTree"] = async (tree, rawOpts) => {
|
|
322
|
+
const incomingOpts = (rawOpts ?? {}) as ResolveOpts
|
|
323
|
+
const t0 = performance.now()
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const slug = regionSlugFromTree(tree)
|
|
327
|
+
const { addressPoints, interpolation } = deps.shards.for(slug)
|
|
328
|
+
const opts: ResolveOpts = {
|
|
329
|
+
...incomingOpts,
|
|
330
|
+
defaultCountry: incomingOpts.defaultCountry ?? deps.defaultCountry,
|
|
331
|
+
...(addressPoints ? { addressPoints } : {}),
|
|
332
|
+
...(interpolation
|
|
333
|
+
? {
|
|
334
|
+
interpolation,
|
|
335
|
+
interpolationRadiusCalibration:
|
|
336
|
+
incomingOpts.interpolationRadiusCalibration ??
|
|
337
|
+
interpCalibrationForRegion(INTERP_RADIUS_CALIBRATION, slug),
|
|
338
|
+
}
|
|
339
|
+
: {}),
|
|
340
|
+
}
|
|
341
|
+
const resolved = await deps.resolver.resolveTree(tree, opts)
|
|
342
|
+
// Best-effort tier metric: read the street node's stamped tier (matches the geocode path).
|
|
343
|
+
const street = resolved.roots.flatMap((r) => collectStreetTier(r)).find(Boolean)
|
|
344
|
+
recordTimed(performance.now() - t0, street ?? "admin")
|
|
345
|
+
|
|
346
|
+
const outcome: ResolveTreeOutcome = { tree: resolved }
|
|
347
|
+
|
|
348
|
+
return outcome
|
|
349
|
+
} catch (err) {
|
|
350
|
+
recordTimed(performance.now() - t0, "error")
|
|
351
|
+
throw err
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Ported from `GeocodeRouter`'s `reloadHandler`.
|
|
356
|
+
const reload: MailwomanAPIEngine["reload"] = async () => {
|
|
357
|
+
const versions = deps.shards.reload()
|
|
358
|
+
|
|
359
|
+
return { reloaded: true, versions }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
engine: { parse, geocode, batch, resolveTree, reload, health },
|
|
364
|
+
preflight: { ok: true },
|
|
365
|
+
}
|
|
366
|
+
}
|
package/cli-kit/index.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The command toolkit for `mailwoman/commands/*` — Pastel/Ink helper types, the one-shot
|
|
7
|
+
* {@linkcode useCommandTask} runner, and the {@linkcode CheckList} renderer. Lives OUTSIDE
|
|
8
|
+
* `commands/` (Pastel treats every file there as a command) and OUTSIDE `sdk/` (`sdk/` submodules
|
|
9
|
+
* mean data acquisition). Built with `createElement`, not JSX, so the module stays plain `.ts` —
|
|
10
|
+
* importable under node's type stripping (the dev `node →` exports condition).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Box, Text } from "ink"
|
|
14
|
+
import { createElement as h, useEffect, useState } from "react"
|
|
15
|
+
import type * as React from "react"
|
|
16
|
+
import type * as zod from "zod"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Type-helper to infer the positional arguments of a command.
|
|
20
|
+
*/
|
|
21
|
+
export type PositionalArguments<T extends zod.ZodTypeAny> = {
|
|
22
|
+
args: zod.infer<T>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* React component for a command with positional arguments.
|
|
27
|
+
*/
|
|
28
|
+
export type PositionalCommandComponent<T extends zod.ZodTypeAny> = React.FC<PositionalArguments<T>>
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type-helper to infer the options of a command.
|
|
32
|
+
*/
|
|
33
|
+
export type CommandProps<
|
|
34
|
+
OptionProps extends zod.ZodObject,
|
|
35
|
+
PositionalProps extends zod.ZodTypeAny | unknown = unknown,
|
|
36
|
+
> = {
|
|
37
|
+
options: zod.infer<OptionProps>
|
|
38
|
+
args: PositionalProps extends zod.ZodTypeAny ? zod.infer<PositionalProps> : unknown[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* React component for a command with options.
|
|
43
|
+
*/
|
|
44
|
+
export type CommandComponent<
|
|
45
|
+
OptionProps extends zod.ZodObject,
|
|
46
|
+
PositionalProps extends zod.ZodTypeAny | unknown = unknown,
|
|
47
|
+
> = React.FC<CommandProps<OptionProps, PositionalProps>>
|
|
48
|
+
|
|
49
|
+
/** The lifecycle of a command's one-shot async task. */
|
|
50
|
+
export type CommandTaskState<T> =
|
|
51
|
+
| { status: "running" }
|
|
52
|
+
| { status: "done"; result: T }
|
|
53
|
+
| { status: "error"; message: string }
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run a command's one-shot async task and own the exit-code discipline: rejection renders the error state and exits 1;
|
|
57
|
+
* resolution exits with `exitCode(result)` (default 0) — always AFTER the final frame committed. Replaces the
|
|
58
|
+
* copy-pasted useEffect/useState/setImmediate dance in every command.
|
|
59
|
+
*/
|
|
60
|
+
/* oxlint-disable react-hooks/exhaustive-deps -- One-shot by design: the task/exitCode closures
|
|
61
|
+
capture their options at mount; tracking them (fresh closure per render) would re-run the task
|
|
62
|
+
every render. The empty/[state] deps are the point. */
|
|
63
|
+
export function useCommandTask<T>(task: () => Promise<T>, exitCode?: (result: T) => number): CommandTaskState<T> {
|
|
64
|
+
const [state, setState] = useState<CommandTaskState<T>>({ status: "running" })
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
void task().then(
|
|
68
|
+
(result) => setState({ status: "done", result }),
|
|
69
|
+
(error: unknown) =>
|
|
70
|
+
setState({ status: "error", message: error instanceof Error ? (error.stack ?? error.message) : String(error) })
|
|
71
|
+
)
|
|
72
|
+
}, [])
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (state.status === "running") return
|
|
76
|
+
const code = state.status === "error" ? 1 : (exitCode?.(state.result) ?? 0)
|
|
77
|
+
setImmediate(() => process.exit(code))
|
|
78
|
+
}, [state])
|
|
79
|
+
|
|
80
|
+
return state
|
|
81
|
+
}
|
|
82
|
+
/* oxlint-enable react-hooks/exhaustive-deps */
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build a guidance-grade error whose rendered form is exactly `message` — no stack. {@linkcode useCommandTask} renders
|
|
86
|
+
* `error.stack ?? error.message`, which is right for unexpected failures but turns deliberate user-facing guidance
|
|
87
|
+
* ("Set $MAILWOMAN_WOF_DB…") into a stack dump. Throw `commandError(msg)` for those instead of `new Error(msg)`.
|
|
88
|
+
*/
|
|
89
|
+
export function commandError(message: string): Error {
|
|
90
|
+
const error = new Error(message)
|
|
91
|
+
error.stack = message
|
|
92
|
+
|
|
93
|
+
return error
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** One ✓/✗ line in a {@linkcode CheckList}. */
|
|
97
|
+
export interface Check {
|
|
98
|
+
ok: boolean
|
|
99
|
+
check: string
|
|
100
|
+
detail?: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The ✓/✗ check-list + PASS/FAIL renderer (extracted from `gazetteer verify`). Pass `verdict` to append the summary
|
|
105
|
+
* line.
|
|
106
|
+
*/
|
|
107
|
+
export function CheckList({ checks, verdict }: { checks: readonly Check[]; verdict?: boolean }): React.ReactElement {
|
|
108
|
+
const lines = checks.map((c, i) =>
|
|
109
|
+
h(
|
|
110
|
+
Text,
|
|
111
|
+
{ key: i, color: c.ok ? "green" : "red" },
|
|
112
|
+
`${c.ok ? "✓" : "✗"} ${c.check}${c.detail ? `: ${c.detail}` : ""}`
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
const summary =
|
|
116
|
+
verdict === undefined
|
|
117
|
+
? null
|
|
118
|
+
: h(
|
|
119
|
+
Text,
|
|
120
|
+
{ color: verdict ? "green" : "red" },
|
|
121
|
+
`${verdict ? "PASS" : "FAIL"} (${checks.filter((c) => c.ok).length}/${checks.length} checks)`
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return h(Box, { flexDirection: "column" }, ...lines, summary)
|
|
125
|
+
}
|