{
  "markdown": "# Rangefind\n\nRangefind is a static search engine for large sites that cannot or should not\nrun a search server.\n\nIt builds a sparse inverted index into static files, then lets the browser query\nthat index with HTTP `Range` requests. The core idea is simple: keep the logical\nsearch shards small, but pack them into a small number of static files so deploys\nand cold starts stay practical.\n\nThis repository is the standalone extraction of a production prototype built for\na large thesis corpus.\n\n## What Is Implemented\n\nThe list below is the compact overview. The **[complete feature guide](docs/features.md)**\nexplains every supported search lane, build mode, runtime, integration, feature\ncombination, fallback, and deliberate boundary with runnable examples.\n\n- JSONL document input.\n- Static-site crawler: `rangefind build ./dist` indexes built HTML directly,\n  with `data-rangefind-*` attributes for body scoping, metadata, facets, and\n  sorts.\n- Schema-driven weighted fields.\n- BM25F-style field scoring.\n- Phrase signals for title/heading fields.\n- Adaptive posting segments.\n- Independently compressed posting segments packed into `terms/packs/*.bin.gz`.\n- Single ESM browser runtime bundle at `dist/runtime.browser.js`.\n- Node runtime (`rangefind/node`) for MCP servers, CLIs, and SSR: same query\n  engine over local index directories (positional file reads) or remote\n  HTTP(S) indexes, with browser-equivalent caching — immutable\n  content-addressed objects cached on disk and in a memory LRU, manifests\n  revalidated with ETags.\n- Mobile runtime (`rangefind/mobile`) for React Native/Hermes and other\n  embedded JS hosts: fully offline search over a bundled or downloaded index\n  via positional file reads, or remote HTTP(S) indexes with memory + persistent\n  caching — see `docs/mobile.md` (covers WebView, Flutter, and native apps\n  too).\n- Lazy paged binary range directories at `terms/directory-root.<hash>.bin.gz`\n  and `terms/directory-pages/*.bin.gz`.\n- ZFS-inspired object pointers with SHA-256 verification before decompression.\n- Content-addressed immutable pack and directory filenames for CDN caching.\n- Exact compressed-object deduplication during index construction.\n- Locality-ordered doc packs with a tiny doc-id ordinal table and dense pointer\n  records for low-transfer result fetching.\n- Dense binary doc-page payload packs for browse, filter, and sort result pages\n  where original document ids are clustered.\n- Range-packed result payloads with capped display fields.\n- Range-addressable posting-block sidecar for high-df terms.\n- Optional authority sidecar for exact title/entity/alias rescue without\n  changing the main inverted index; a single title authority also supplies\n  bounded prefix autocomplete without a second large build structure.\n- Range-packed binary facet dictionaries for high-cardinality metadata.\n- File-backed build pipeline with immutable posting segments, selected-term\n  spools, raw/compressed document spools, heap-based segment merge, and sampled\n  build telemetry.\n- Minimal runtime manifest with lazy full-manifest and telemetry sidecars.\n- Browser runtime with adaptive HTTP `Range` coalescing and bounded overfetch.\n- Main-index typo correction that uses bounded vocabulary shard probes and\n  executes only the strongest corrected searches when first-page results are\n  empty or weak.\n- Multi-value keyword facets with lazy dictionary loading.\n- Range-addressed typed numeric, date, and boolean doc-values for filters and\n  sorting.\n- Lazy binary sorted doc-value trees for range-pruned top-k sort and doc-order\n  early-stop browsing.\n- Lucene-style geo point fields with a range-addressed static KD tree:\n  bounding-box and radius filters, exact nearest-neighbor distance sort with\n  early-stop proofs (with or without a text query), text-plus-geo filtering,\n  per-cell filter summaries, and distance boosts.\n- Optional compact geo result capsules and multi-resolution facet-to-cell\n  indexes for one-range map results and category-aware spatial routing, plus a\n  wildcard occupancy lane for arbitrary names and brands.\n- Generic route-corridor search over encoded polylines, coordinate arrays, or\n  GeoJSON: exact point-to-route filtering, multi-resolution corridor cells,\n  progress/direction/viewport ranking, and a rejoin point for every result.\n- Static routing and itinerary planning (`rangefind/route`): a CRP/MLD\n  route graph built from OSM road networks into range-addressed cell and\n  overlay objects — point-to-point driving routes with geometry and named\n  steps, travel-time matrices, and multi-stop itinerary optimization, all\n  exact versus a full-graph Dijkstra and served from static files with a\n  bounded fetch set per query; sharded builds share one top overlay and\n  route identically to monolithic ones — see `docs/route-graph.md`.\n- Search-as-you-type autocomplete inside the range-addressed authority index:\n  diacritic-folded prefix and mid-label token matching, popularity or custom\n  weights, exact best-first top-k with per-shard weight proofs, and\n  precomputed hot lists so a first keystroke costs one small fetch—without a\n  second suggestion pack family or a corpus-sized heap map.\n- Hybrid semantic search from a range-addressed IVF vector index: int8\n  quantized embeddings, coarse-dimension candidate pages plus a fixed-width\n  full-dimension refine store, cosine top-k, and reciprocal-rank fusion with\n  the text lane — no server, no vector database.\n- Analyzer-consistent snippets and highlight ranges on search results.\n- Multilingual analysis (`analysis` config): per-document language detection\n  or an explicit language field, per-language light stemmers and stopwords\n  for 20+ languages, script-aware Unicode folding (Latin, Greek, Cyrillic,\n  Arabic, Hebrew, Devanagari), dictionary-free CJK bigram tokenization that\n  is deterministic across Node and every browser, cross-language query\n  expansion with index-evidence base-plan selection, and the whole profile\n  frozen into the manifest so query analysis always matches the index.\n- Incremental publishing: `rangefind build --update` adds small delta\n  generations over an existing index — unchanged pack bytes keep their\n  content-addressed names (and CDN cache entries), replaced documents\n  tombstone their old version, and the runtime merges every lane (text,\n  sorted browse, geo, vector, hybrid, suggest, facets) across generations\n  with scores that stay exactly comparable. `rangefind build --compact`\n  folds generations back into a single index when they pile up.\n- Per-query facet counts with exact-or-flagged semantics: dictionary-backed\n  global counts, exact counts over budgeted match sets, and bounded\n  chunk-sampled estimates for very large result sets.\n- OpenStreetMap place and address fixture with canonical zero-posting address\n  lookup, compact `addr:interpolation` ranges, structured forward and reverse\n  geocoding, bounded multi-provider civic/postal augmentation (including the\n  Québec RQA adapter), reusable\n  `rangefind/osm` and `rangefind/osm/node` integration APIs, and exhaustive geo\n  oracles.\n- OSM typed and natural-language constraints for accessibility, services,\n  payment, seating, reservations, fees, and open-now; conservative client-side\n  `opening_hours` evaluation; route-aware place search; compact place details;\n  and simplified encoded area geometry for real polygon/line map rendering.\n- Geographic index sharding for planet-scale corpora: independently built and\n  updated per-region shards sharing one frozen scoring-stats artifact\n  (`scoringStats` config + `rangefind/scoring-stats`), a tiny sharded root\n  manifest (`rangefind/shards`), lazy shard engines with bbox geo routing and\n  expanding nearest-first search, and merged lanes whose rankings match a\n  monolithic build of the same corpus exactly — see `docs/sharded-osm.md`.\n- Link-graph authority (`linkRank`): the static-site crawler resolves internal\n  `<a href>` links into a document graph and folds a build-time PageRank prior\n  into a sortable numeric doc-value. Well-linked pages win near-ties in ranking\n  (an opt-out multiplicative boost, `score *= 1 + boost*linkRank`, applied as a\n  bounded window rerank with no change to the scoring hot loop), you can sort by\n  authority, and there is zero query-time graph traversal. Any corpus that knows\n  its own edges can opt in with the turnkey `examples/link-graph-enrich.mjs`\n  enricher (or the exported `rangefind/link-graph` helper); see\n  `docs/link-graph.md`.\n- Tiny runnable example.\n\n## Why This Exists\n\nMost browser search libraries assume the browser downloads a whole index, or\nthey optimize for smaller sites. Rangefind is for the case where you want:\n\n- no backend service,\n- no hosted search provider,\n- static hosting compatibility,\n- measurable retrieval quality,\n- low request count despite many logical shards,\n- and an index format designed around top-k retrieval.\n\n## Quick Start\n\nDemos: https://rangefind.dev/ — an OpenStreetMap map search backed by the\nrolling public sharded index (autocomplete, viewport geo queries, and\nnearest-neighbor) and the minimal example, both served as pure static files.\n\nThe already-generated planet-scale OSM index is free to query directly from\nbrowsers or Node—no account or API key required:\n\n```js\nimport { createSearch } from \"rangefind\";\n\nconst engine = await createSearch({\n  baseUrl: \"https://osm.rangefind.dev/\"\n});\n```\n\nSee its live coverage and freshness at https://osm.rangefind.dev/. It is a\nbest-effort public service; self-host the index when you need guaranteed\ncapacity, version pinning, custom fields, or an operational SLA. OSM result\nUIs must display\n[`© OpenStreetMap contributors`](https://www.openstreetmap.org/copyright).\n\n```bash\nnpm install\nnpm run build:example\nnpm run test:smoke\nnpm run serve:example\n```\n\nOpen `http://localhost:5178/`.\n\n## Documentation\n\nStart with the guide that matches the job:\n\n| Guide | Use it for |\n| --- | --- |\n| **[Feature guide](docs/features.md)** | Complete capability catalog, examples, supported combinations, compatibility, and boundaries. |\n| **[Reference](docs/reference.md)** | Configuration properties, builder/runtime APIs, tuning knobs, UI component, and deployment requirements. |\n| **[Architecture](docs/architecture.md)** | Static file layout, range directories, posting retrieval, geo, authority, and vector internals. |\n| **[Autocomplete guide](docs/autocomplete.md)** | Search-as-you-type patterns, measured per-keystroke costs, hydrated previews, instant selection, and the anti-patterns. |\n| **[OSM example](examples/osm-geo/README.md)** | Map search, autocomplete, geocoding, constraints, route corridors, geometry, extraction, and RQA. |\n| **[Replace Google Maps APIs](https://rangefind.dev/google-maps-api-alternative/)** | Free public OSM index, migration adapter, API mapping, complete use cases, production checklist, attribution, and parity boundaries. |\n| **[Node](docs/node-runtime.md) / [mobile](docs/mobile.md)** | Local, remote, cached, embedded, and offline runtimes. |\n| **[Sharded OSM](docs/sharded-osm.md)** | Planet-scale regional builds, root routing, and exact score comparability. |\n| **[Route graph](docs/route-graph.md)** | Static routing, travel-time matrices, and itinerary planning over range-addressed road-network cells and overlays. |\n| **[Production maps benchmark](docs/osm-maps-benchmark.md)** | Google Maps-style workload coverage, quality assertions, and phone budgets. |\n\n## Crawl A Static Site\n\nIf you already have a built static site (from any generator), point Rangefind at\nthe output directory and it indexes the HTML directly — no JSONL to author:\n\n```bash\nnpx rangefind build ./dist\n```\n\nThis crawls every `.html`/`.htm` file under `./dist`, extracts the title,\nheadings, and main body text, and writes the index to `./dist/rangefind` (change\nit with `--output`). Result URLs default to site-root paths (`--base-url` sets a\nprefix or origin, e.g. `--base-url https://example.com/`). Deploy the site and\nload the index with `createSearch({ baseUrl: \"/rangefind/\" })`.\n\nContent selection follows the document: the crawler indexes `<main>`, else\n`<article>`, else `<body>`, and always drops `<script>`, `<style>`, `<nav>`, and\n`<aside>`. `<header>`/`<footer>` are dropped only when the fallback `<body>`\nregion is used (no `<main>`/`<article>`), so an in-article post header and its\n`<h1>` are still indexed. Opt-in `data-rangefind-*` attributes\n(mirroring Pagefind's vocabulary) tune it:\n\n| Attribute | Effect |\n| --- | --- |\n| `data-rangefind-body` | Index only the text inside marked elements. |\n| `data-rangefind-ignore` | Drop this subtree (on `<html>`/`<body>` skips the page). |\n| `data-rangefind-meta=\"name\"` | Capture element text as metadata `name` (`\"name:attr\"` reads an attribute). |\n| `data-rangefind-filter=\"key\"` | Add the element text as a facet value under `key`. |\n| `data-rangefind-sort=\"key\"` | Use the element text as a sortable value `key`. |\n\nPer-document language comes from `<html lang=\"…\">` and the page description from\n`<meta name=\"description\">`. See the\n[reference guide](docs/reference.md#crawling-a-static-site) for the full rules.\n\n## Search UI Component\n\nPair the crawler with a drop-in search box. `<rangefind-search>` is a\nframework-agnostic Web Component: two lines of HTML give any static site a\npolished, accessible search box (WAI-ARIA combobox, keyboard navigation,\ninstant results, autocomplete).\n\n```html\n<script type=\"module\" src=\"https://unpkg.com/rangefind/dist/rangefind-search.js\"></script>\n<rangefind-search src=\"/rangefind/\"></rangefind-search>\n```\n\nIt renders into its **light DOM** and ships **no styling** of its own, so it\ncomposes with whatever CSS you already use. Style it three ways: the optional\ntheme stylesheet, your own CSS via the `rf-search*` hook classes, or utility\nclasses passed per part. Here it is styled purely with Tailwind — no theme CSS:\n\n```html\n<script src=\"https://cdn.tailwindcss.com\"></script>\n<script type=\"module\" src=\"https://unpkg.com/rangefind/dist/rangefind-search.js\"></script>\n\n<rangefind-search\n  src=\"/rangefind/\"\n  placeholder=\"Search…\"\n  input-class=\"w-full rounded-lg border px-3 py-2\"\n  panel-class=\"absolute inset-x-0 mt-2 rounded-lg border bg-white shadow-xl\"\n  option-class=\"block rounded px-3 py-2 hover:bg-slate-100 aria-selected:bg-blue-50\"\n  option-title-class=\"font-semibold\"\n  option-snippet-class=\"text-sm text-slate-500\"\n  mark-class=\"bg-yellow-200\"></rangefind-search>\n```\n\nPrefer the batteries-included look? Link the opt-in theme (light + dark) and\nskip the `*-class` attributes:\n\n```html\n<link rel=\"stylesheet\" href=\"https://unpkg.com/rangefind/dist/rangefind-search.css\">\n<rangefind-search src=\"/rangefind/\" placeholder=\"Search…\"></rangefind-search>\n```\n\nWorks in plain HTML, React, Vue, Svelte, and Angular — Web Components are\nuniversal. It emits `rangefind:search`, `rangefind:select`, and\n`rangefind:error` events, and takes `.classNames` / `.searchOptions` JS\nproperties for programmatic use. Build the bundle with `npm run build:element`;\ntry it against the basic example (`npm run build:example && npm run\nserve:example`) at `/component.html` (theme) and `/component-tailwind.html`\n(Tailwind). See the\n[reference guide](docs/reference.md#search-ui-component) for every attribute,\nthe full class-hook list, events, and accessibility notes.\n\n## Static Site Generator Adapters\n\nThe crawler and the search component compose naturally with any static site\ngenerator's own build lifecycle — an adapter runs the crawl right after the\ngenerator writes its output and drops the client assets in for you, so there's\nnothing to wire up by hand beyond installing it.\n\n| Generator | Package | How it hooks in |\n| --- | --- | --- |\n| [Astro](https://astro.build) | [`rangefind-astro`](packages/rangefind-astro) | Real integration on `astro:build:done`, plus a `<RangefindSearch />` component. |\n| [Eleventy](https://www.11ty.dev) | [`eleventy-plugin-rangefind`](packages/eleventy-plugin-rangefind) | Real plugin on `eleventy.after`, plus a universal `{% rangefindSearch %}` shortcode. |\n| [Docusaurus](https://docusaurus.io) | [`docusaurus-plugin-rangefind`](packages/docusaurus-plugin-rangefind) | Real plugin on `postBuild` + `injectHtmlTags`; drop `<rangefind-search>` into a navbar item or MDX. |\n| [Hugo](https://gohugo.io) | [`integrations/hugo`](integrations/hugo) | Hugo has no plugin loader, so this is a `hugo && rangefind build public` recipe plus a copy-paste partial. |\n| [MkDocs](https://www.mkdocs.org) | [`mkdocs-rangefind`](integrations/mkdocs-rangefind) (PyPI) | A real Python plugin (`on_post_build` / `on_post_page`) that shells out to the Node CLI and auto-injects the widget into every page. |\n\nAstro, Eleventy, and Docusaurus are each real, independently installable\npackages under `packages/` (built with zero dependencies beyond the target\ngenerator itself); Hugo and MkDocs live under `integrations/` since neither\nhas an npm-based plugin system of its own. Every one of them is verified\nend to end against the real tool — a real Astro/Eleventy/Docusaurus build, a\nHomebrew-installed Hugo binary, and a pip-installed MkDocs — crawling a\nfixture site and confirming the resulting index actually answers a search\nquery. See each package's own README for install instructions and options.\n\n## Full Wikipedia Search Site\n\nThe `examples/wiki-search` project is a fuller static search application for\nWikimedia article dumps. It defaults to the latest English Wikipedia articles\ndump and can run on a bounded sample or the full dump:\n\n```bash\nnpm run build:wiki-site -- --limit=50000\nnpm run serve:wiki-site\n```\n\nUse `npm run build:wiki-site:full` to build the full default dump with the\nbounded body cap, or `npm run build:wiki-site:fr:full` for the full French\nWikipedia dump. The generated site lives at `examples/wiki-search/public/` and\nkeeps the same static hosting requirement as every Rangefind index: the host\nmust support HTTP `Range` requests for `.bin.gz` files.\n\n## French Wikipedia Scalability Fixture\n\nRangefind includes a reproducible French Wikipedia fixture that streams the\nofficial Wikimedia article dump, builds a static site, and benchmarks query\nlatency, request count, and transfer size:\n\n```bash\nnpm run build:browser\nnode scripts/frwiki_fixture.mjs all --limit=50000 --runs=3\n```\n\nFor runtime-only changes, reuse the existing generated index and rerun just the\nrequest/transfer benchmark:\n\n```bash\nnode scripts/frwiki_fixture.mjs runtime-bench --limit=50000 --runs=3\n```\n\nUse `--limit=0` to run against the full dump. The generated site lives at\n`examples/frwiki/public/`. The fixture validates text query top-k against the\nexact retrieval path by default and records cold request counts, transfer bytes,\nruntime posting-block stats, typed filter/sort validation, and scale reports\nacross multiple Wikipedia sample sizes.\nExtracted JSONL is cached under `examples/frwiki/data/`, so later runs with the\nsame dump/body cap and an equal or smaller limit do not stream the dump again.\nEach generated index includes `manifest.min.json`, `manifest.full.json`, and\n`debug/build-telemetry.json`. The full diagnostic `manifest.json` is still\nwritten for local inspection and records phase timings, sampled memory peaks,\nCPU time, disk byte deltas, and segment counters.\n\n## Build A Custom Index\n\n> For the complete configuration schema, every tuning knob, the full runtime\n> API, and deployment requirements, see the\n> **[reference guide](docs/reference.md)**. The walkthrough below is the\n> quick version.\n\nCreate newline-delimited JSON:\n\n```json\n{\"id\":\"1\",\"url\":\"/a\",\"title\":\"Static search\",\"body\":\"Search without a server\",\"category\":\"docs\",\"tags\":[\"static\",\"range\"],\"year\":2026,\"published\":\"2026-01-10\",\"featured\":true}\n{\"id\":\"2\",\"url\":\"/b\",\"title\":\"Range packs\",\"body\":\"Use HTTP byte ranges\",\"category\":\"index\",\"tags\":[\"range\"],\"year\":2026,\"published\":\"2026-02-01\",\"featured\":false}\n```\n\nCreate `rangefind.config.json`:\n\n```json\n{\n  \"input\": \"docs.jsonl\",\n  \"output\": \"public/rangefind\",\n  \"idPath\": \"id\",\n  \"urlPath\": \"url\",\n  \"indexProfile\": \"static-large\",\n  \"targetPostingsPerDoc\": 12,\n  \"bodyIndexChars\": 6000,\n  \"alwaysIndexFields\": [\"title\", \"category\"],\n  \"typoMode\": \"main-index\",\n  \"typoTrigger\": \"zero-or-weak\",\n  \"typoMaxEdits\": 2,\n  \"typoMaxTokenCandidates\": 8,\n  \"typoMaxQueryPlans\": 5,\n  \"typoMaxCorrectedSearches\": 3,\n  \"typoMaxShardLookups\": 12,\n  \"display\": [\"id\", \"url\", \"title\", \"body\", \"category\", \"tags\", \"year\", \"published\", \"featured\"],\n  \"fields\": [\n    { \"name\": \"title\", \"path\": \"title\", \"weight\": 4.5, \"b\": 0.55, \"phrase\": true },\n    { \"name\": \"body\", \"path\": \"body\", \"weight\": 1.0, \"b\": 0.75 }\n  ],\n  \"authority\": [\n    { \"name\": \"title\", \"path\": \"title\" },\n    { \"name\": \"aliases\", \"path\": \"aliases\" }\n  ],\n  \"facets\": [\n    { \"name\": \"category\", \"path\": \"category\" },\n    { \"name\": \"tags\", \"path\": \"tags\" }\n  ],\n  \"numbers\": [\n    { \"name\": \"year\", \"path\": \"year\", \"type\": \"int\" },\n    { \"name\": \"published\", \"path\": \"published\", \"type\": \"date\" }\n  ],\n  \"booleans\": [\n    { \"name\": \"featured\", \"path\": \"featured\" }\n  ]\n}\n```\n\n`display` controls only the payload returned with search results. Indexed fields\ncan stay long while returned fields are capped, for example:\n\n```json\n{ \"name\": \"body\", \"path\": \"body\", \"maxChars\": 640 }\n```\n\nFor large static corpora, `targetPostingsPerDoc` is the body-term budget.\n`bodyIndexChars` caps only the text considered by the indexer, while display\npayload size stays controlled by `display` entries. Terms from\n`alwaysIndexFields` are indexed before the body budget is applied.\n\n`typoMode: \"main-index\"` uses the normal term vocabulary for typo candidates\ninstead of building a separate typo sidecar. Use `typoMode: \"off\"` to disable\ncorrection.\n\n`authority` fields build a packed lexicon for canonical labels such as\ntitles, entity names, product names, slugs, and aliases. The runtime first tries\na diacritic-preserving surface-exact key, then falls back to folded exact and\ntoken keys only when needed, so common title rescue stays precise and cheap\nwithout forcing all label logic into the BM25 posting lists. `suggest` fields\nstream into the same external-run reducer under an autocomplete namespace, so\nmulti-million-document builds never create a duplicate in-memory title map or\na separate `suggest/` pack family.\n\nBuild:\n\n```bash\nnpx rangefind build --config rangefind.config.json\n```\n\nQuery in the browser:\n\n```js\nimport { createSearch } from \"rangefind\";\n\nconst engine = await createSearch({ baseUrl: \"/rangefind/\" });\nconst result = await engine.search({ q: \"static search\", size: 10 });\nconsole.log(result.results);\n```\n\nFilters and sort use range-addressed doc-value columns:\n\n```js\nconst result = await engine.search({\n  q: \"static search\",\n  filters: {\n    facets: { tags: [\"range\"] },\n    numbers: { published: { min: \"2026-01-01\" } },\n    booleans: { featured: true }\n  },\n  sort: { field: \"published\", order: \"desc\" }\n});\n```\n\nGeo fields index one point per document into a range-addressed static KD tree\n(Lucene `LatLonPoint`-style, E7 fixed precision):\n\n```json\n{\n  \"geo\": [{ \"name\": \"location\", \"latPath\": \"lat\", \"lonPath\": \"lon\" }]\n}\n```\n\n```js\n// Places inside a map viewport (empty-query browse).\nawait engine.search({ q: \"\", geo: { box: { minLat: 45.45, maxLat: 45.62, minLon: -73.7, maxLon: -73.45 } } });\n\n// Exact nearest neighbors, sorted by distance with early-stop proofs.\nawait engine.search({ q: \"\", geo: { near: { lat: 45.5017, lon: -73.5673 }, sort: \"distance\" } });\n\n// Exact nearest matches for a text query (\"closest bakeries first\").\nawait engine.search({ q: \"bakery\", geo: { near: { lat: 45.5017, lon: -73.5673 }, sort: \"distance\" } });\n\n// Text search restricted to a radius, plus a Lucene-style distance boost.\nawait engine.search({\n  q: \"bakery\",\n  geo: {\n    near: { lat: 45.5017, lon: -73.5673, radiusMeters: 5000 },\n    boost: { weight: 2, pivotMeters: 500 }\n  }\n});\n```\n\nResults include `distanceMeters` whenever `geo.near` is present. Radius and\nbox filters are exact (bounding-box prune plus Haversine verification), and\n`stats` reports the tree traversal (`geoLane`, `geoLeavesVisited`,\n`geoPointsScanned`, ...). See `examples/osm-geo/` for an OpenStreetMap-scale\nexample and benchmark.\n\nWeighted and mid-label search-as-you-type suggestions use the authority\nlexicon:\n\n```json\n{\n  \"suggest\": [{ \"path\": \"title\" }]\n}\n```\n\n```js\nconst { suggestions } = await engine.suggest({ q: \"boul\", size: 8, hydrate: true });\n// [{ text: \"Boulangerie Fischer\", weight: 12, count: 12, doc: 41,\n//    result: { title: \"Boulangerie Fischer\", url: \"…\", … } }, ...]\n```\n\nMatching is prefix-based over diacritic-folded keys (\"montre\" finds\n\"Montréal\") and covers mid-label tokens (\"eiffel\" finds \"Tour Eiffel\").\nRanking uses an optional `weightPath` (for example population or importance)\nand falls back to direct-prefix priority plus popularity — how many documents\nshare the surface. Each keystroke costs at most a few small range requests;\nrepeat keystrokes in a session are usually served entirely from cache.\n\nSuggestions also carry the best documents behind each surface (`doc`/`docs`),\nand `hydrate: true` resolves them into real search hits — the same display\npayloads `search()` returns — so a dropdown can show result cards and a\nselected suggestion renders instantly instead of re-running the query as a\nsearch (measured 0.5 KB and 4 ms, versus 215 KB and 32 ms re-searching its\ntext). The **[autocomplete guide](docs/autocomplete.md)** covers the patterns,\nwhat each costs per keystroke, and when to prefer focused previews over\nhydrating every row.\n\nFor title-prefix completion on an older index, a single\n`authority: [{ \"name\": \"title\", \"path\": \"title\" }]` field is also sufficient.\nThe runtime walks the first matching authority shards in key order and hydrates\nonly the requested titles. New builds should configure `suggest` to get custom\nweights, popularity fallback, mid-label completion, and hot-prefix lists in the\nsame authority packs.\n\nFacet counts for filter UIs come back with the search response:\n\n```js\nconst result = await engine.search({ q: \"static\", facets: [\"tags\"] });\n// result.facets.tags = { values: [{ value: \"range\", label: \"range\", count: 12 }, ...], exact: true }\n```\n\nHybrid semantic search takes a query embedding from the host (for example\ntransformers.js in the browser) and fuses it with the text lane:\n\n```js\nconst result = await engine.search({ q: \"map search\", vector: queryEmbedding });\nconst nearest = await engine.vectorSearch({ vector: queryEmbedding, k: 10 });\n```\n\nTypo fallback is automatic. For example, if `statik search` has no exact\nfirst-page hits but `static search` does, the response includes:\n\n```js\n{\n  correctedQuery: \"static search\",\n  corrections: [{ from: \"statik\", to: \"static\", surface: \"static\" }]\n}\n```\n\n## Static Hosting Requirement\n\nThe runtime expects the host to support HTTP `Range` requests for `.bin.gz` files.\nGitHub Pages supports this. The included local server also supports it.\n\n## Development\n\n```bash\nnpm run check\nnpm test\nnpm run test:smoke\nnpm run test:all\nnpm run bench:quality\nnpm run bench:performance\nnpm run bench:directories -- --index=/path/to/public/rangefind\nnpm run bench:osm-maps\n```\n\nThe unit tests cover analyzer normalization, binary varint/fixed-width codecs,\nconfig resolution, shard/range planning, term/code binary round-trips, and an\nend-to-end build plus browser-runtime query against a local HTTP `Range` server.\nThe end-to-end build test also verifies emitted build telemetry and file-backed\nspool counters in the manifest.\n\nThe benchmark scripts are dependency-free and run against the example static\nsite. `bench:quality` reports known-item and typo-recovery Hit@k/MRR plus\nstructured filter/sort checks for facets, dates, booleans, and signed numbers.\n`bench:performance` reports query latency, HTTP request count, and transfer size.\n`bench:directories` compares global, naive prefix, and paged range-directory\nlayouts against an existing built index.\n`bench:osm-maps` runs the weighted common map-search workload documented in\n[`docs/osm-maps-benchmark.md`](docs/osm-maps-benchmark.md), including locality,\nnearest-first, viewport, shard fan-out, and phone-budget checks.\n`docs/performance-research.md` tracks the top-k retrieval papers currently\nguiding format decisions.\n\n## Project Direction\n\nPlanned next milestones:\n\n- Pagefind/Lucene/SQLite benchmark package,\n- build-time sparse expansion hooks,\n- WASM-free and WASM-assisted runtime comparisons,\n- CI release workflow + published npm package.\n\nContributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\n[MIT](LICENSE) © Xavier Jodoin\n",
  "bytes": 28670,
  "sha": "955deb17669e90ea4152e00a4eaf2bb34d6d7c35514b139b4107c86668579772",
  "repo_slug": "xjodoin/rangefind",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_xjodoin_rangefind_08448366/readme"
}