/* * types.ts — the search contracts (D-03, D-07, D-08, D-11). * -------------------------------------------- * GazetteerEntry / SearchQuery / SearchHit / SearchSource are the seam * every backend implements: the in-Worker gazetteer (backend #1, this * phase), the Malaysia-only WASM geocoder (backend #2, D-05, post-v1), * and Photon-on-VPS (backend #3, D-05, Phase 5). Backend #2/#3 do NOT * ship in this phase — the interface exists so they can be added without * a rewrite. * * SearchSource.search() returns an EMPTY array as a legitimate, * load-bearing value: "no match in the KL gazetteer" (D-13's * out-of-area negatives must be refused honestly, never answered with * the nearest KL pin). * * byId() is separate and SYNCHRONOUS: the alternates callback handler * and the webview's deep-link reader both need an id -> entry lookup and * neither should await a network. * * Type-only module: no runtime values, no imports, no storage (GEO-03). */ /** One curated gazetteer landmark (D-07 typed JSON, D-08 native aliases). */ export interface GazetteerEntry { /** Stable, slug-shaped: "suria-klcc", "sunway-pyramid". Never renamed once shipped. */ id: string; /** Canonical display name (the OSM landmark name, e.g. "Suria KLCC"). */ name: string; /** Locals' shorthand (D-08) — never empty; always includes the canonical name. */ aliases: string[]; lat: number; lng: number; /** "mall" | "office-tower" | "park" | "station" | ... (small closed set). */ category: string; /** Human address for sendVenue's `address` field — shown VERBATIM, never normalized. */ address?: string; } /** A search request. */ export interface SearchQuery { /** Raw user query. */ text: string; /** Default 5 (1 best + 2-4 alternates, D-02). */ limit?: number; } /** One ranked result. */ export interface SearchHit { entry: GazetteerEntry; /** 0..1, higher is better. */ score: number; /** Which alias matched — the corpus report needs this provenance. */ matchedAlias: string; } /** The pluggable search seam (D-03). Backends: "gazetteer" | "wasm-geocoder" | "photon". */ export interface SearchSource { readonly name: string; /** EMPTY array = honest refusal (D-13) — never a nearest-match pin. */ search(q: SearchQuery): Promise; /** Synchronous id -> entry, honest null when unknown. */ byId(id: string): GazetteerEntry | null; }