thaizip 0.5.1 → 0.7.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/normalizer.ts","../src/core/trigrams.ts","../src/core/indexer.ts","../src/data/loader.ts","../src/core/search.ts","../src/core/formatter.ts","../src/core/resolver.ts","../src/react/useThaiAddressAutocomplete.ts"],"names":["useState","useRef","useCallback","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["const TONE_MARKS = /[\\u0E48-\\u0E4B\\u0E47\\u0E4C]/g\nconst ADDRESS_PREFIXES = /^(จังหวัด|อำเภอ|ตำบล|แขวง|เขต)/\n\nexport function normalizeThaiAddressText(input: string): string {\n if (!input) return ''\n let text = input.trim()\n text = text.replace(ADDRESS_PREFIXES, '').trim()\n text = text.replace(TONE_MARKS, '')\n text = text.toLowerCase()\n return text\n}\n","import { normalizeThaiAddressText } from './normalizer'\n\n// Public: for callers who start from raw (un-normalised) text — e.g. indexer\nexport function extractTrigrams(text: string): Set<string> {\n return extractTrigramsNormalized(normalizeThaiAddressText(text))\n}\n\n// Internal: for callers who already hold a normalised string — e.g. search.ts\nexport function extractTrigramsNormalized(text: string): Set<string> {\n const result = new Set<string>()\n if (text.length === 0) return result\n if (text.length < 3) {\n result.add(text)\n return result\n }\n for (let i = 0; i <= text.length - 3; i++) {\n result.add(text.slice(i, i + 3))\n }\n return result\n}\n","import { extractTrigrams } from './trigrams'\nimport type { RawData, ThaiAddressRecord, TrigramIndex } from '../types'\n\nexport function buildThaiAddressIndex(data: RawData): TrigramIndex {\n const { provinces, amphures, tambons } = data\n\n // Build lookup maps (filter deleted)\n const provMap = new Map(\n provinces.filter(p => !p.deleted_at).map(p => [p.id, p])\n )\n const ampMap = new Map(\n amphures.filter(a => !a.deleted_at).map(a => [a.id, a])\n )\n\n const records: ThaiAddressRecord[] = []\n const map = new Map<string, Set<number>>()\n const zipIndex = new Map<string, number[]>()\n\n for (const tambon of tambons) {\n if (tambon.deleted_at) continue\n\n const amphure = ampMap.get(tambon.amphure_id)\n if (!amphure) {\n console.warn(\n `[thaizip] skipping tambon id=${tambon.id} (${tambon.name_th}): ` +\n `amphure_id=${tambon.amphure_id} not found or deleted`\n )\n continue\n }\n\n const province = provMap.get(amphure.province_id)\n if (!province) continue\n\n const record: ThaiAddressRecord = {\n provinceId: province.id,\n provinceNameTh: province.name_th,\n provinceNameEn: province.name_en,\n amphureId: amphure.id,\n amphureNameTh: amphure.name_th,\n amphureNameEn: amphure.name_en,\n tambonId: tambon.id,\n tambonNameTh: tambon.name_th,\n tambonNameEn: tambon.name_en,\n zipCode: String(tambon.zip_code),\n }\n\n const idx = records.length\n records.push(record)\n\n // Build zip index\n const existing = zipIndex.get(record.zipCode)\n if (existing) existing.push(idx)\n else zipIndex.set(record.zipCode, [idx])\n\n const fields = [\n record.tambonNameTh,\n record.tambonNameEn,\n record.amphureNameTh,\n record.amphureNameEn,\n record.provinceNameTh,\n record.provinceNameEn,\n record.zipCode,\n ]\n\n for (const field of fields) {\n for (const trigram of extractTrigrams(field)) {\n let set = map.get(trigram)\n if (!set) {\n set = new Set()\n map.set(trigram, set)\n }\n set.add(idx)\n }\n }\n }\n\n return { map, records, zipIndex }\n}\n","import { buildThaiAddressIndex } from '../core/indexer'\nimport type { TrigramIndex } from '../types'\nimport type { CompactProvince, CompactAmphure, CompactTambon } from './defaultData'\n\nlet cached: TrigramIndex | null = null\n\nexport function clearDefaultIndex(): void {\n cached = null\n}\n\nexport async function loadDefaultIndex(): Promise<TrigramIndex> {\n if (cached) return cached\n const { p, a, t } = await import('./defaultData') as {\n p: CompactProvince[]\n a: CompactAmphure[]\n t: CompactTambon[]\n }\n cached = buildThaiAddressIndex({\n provinces: p.map(([id, name_th, name_en]) => ({ id, name_th, name_en, geography_id: 0, deleted_at: null })),\n amphures: a.map(([id, name_th, name_en, province_id]) => ({ id, name_th, name_en, province_id, deleted_at: null })),\n tambons: t.map(([id, name_th, name_en, amphure_id, zip_code]) => ({ id, name_th, name_en, amphure_id, zip_code: Number(zip_code), deleted_at: null })),\n })\n return cached\n}\n","import { normalizeThaiAddressText } from './normalizer'\nimport { extractTrigramsNormalized } from './trigrams'\nimport type { SearchOptions, ThaiAddressRecord, TrigramIndex } from '../types'\n\nconst ZIP_CODE_RE = /^\\d+$/\n\nexport function searchThaiAddress(\n index: TrigramIndex,\n query: string,\n options?: SearchOptions,\n): ThaiAddressRecord[] {\n const limit = options?.limit ?? 10\n const threshold = options?.threshold ?? 0.4\n\n if (!index || !query) return []\n const normalized = normalizeThaiAddressText(query)\n if (normalized.length === 0) return []\n if (normalized.length > 300) return []\n\n // Special case: zip code query (require at least 2 digits to avoid overly broad matches)\n if (ZIP_CODE_RE.test(normalized)) {\n if (normalized.length < 2) return []\n const matches: ThaiAddressRecord[] = []\n for (const [zip, indices] of index.zipIndex) {\n if (zip.startsWith(normalized)) {\n for (const idx of indices) matches.push(index.records[idx])\n }\n }\n // exact match first, then ascending zip code\n matches.sort((a, b) => {\n if (a.zipCode === normalized && b.zipCode !== normalized) return -1\n if (b.zipCode === normalized && a.zipCode !== normalized) return 1\n return a.zipCode.localeCompare(b.zipCode)\n })\n return matches.slice(0, limit)\n }\n\n const queryTrigrams = extractTrigramsNormalized(normalized)\n\n // Accumulate hit counts per record index\n const hits = new Map<number, number>()\n for (const trigram of queryTrigrams) {\n const candidates = index.map.get(trigram)\n if (!candidates) continue\n for (const idx of candidates) {\n hits.set(idx, (hits.get(idx) ?? 0) + 1)\n }\n }\n\n const scored: { idx: number; score: number }[] = []\n for (const [idx, count] of hits) {\n const score = count / queryTrigrams.size\n if (score >= threshold) {\n scored.push({ idx, score })\n }\n }\n\n scored.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score\n const ra = index.records[a.idx]\n const rb = index.records[b.idx]\n return (\n ra.provinceNameTh.localeCompare(rb.provinceNameTh) ||\n ra.amphureNameTh.localeCompare(rb.amphureNameTh) ||\n ra.tambonNameTh.localeCompare(rb.tambonNameTh)\n )\n })\n\n return scored.slice(0, limit).map(({ idx }) => index.records[idx])\n}\n","import type { ThaiAddressRecord, ThaiAddressSuggestion } from '../types'\n\nexport function formatThaiAddressSuggestion(record: ThaiAddressRecord): ThaiAddressSuggestion {\n return {\n id: String(record.tambonId),\n label: `${record.tambonNameTh} > ${record.amphureNameTh} > ${record.provinceNameTh} ${record.zipCode}`,\n tambon: record.tambonNameTh,\n tambonEn: record.tambonNameEn,\n amphure: record.amphureNameTh,\n amphureEn: record.amphureNameEn,\n province: record.provinceNameTh,\n provinceEn: record.provinceNameEn,\n zipCode: record.zipCode,\n }\n}\n","import type { ThaiAddressRecord, ResolvedThaiAddress } from '../types'\n\nexport function resolveThaiAddress(record: ThaiAddressRecord): ResolvedThaiAddress {\n return {\n tambon: record.tambonNameTh,\n tambonEn: record.tambonNameEn,\n amphure: record.amphureNameTh,\n amphureEn: record.amphureNameEn,\n province: record.provinceNameTh,\n provinceEn: record.provinceNameEn,\n zipCode: record.zipCode,\n subdistrict: record.tambonNameTh,\n subdistrictEn: record.tambonNameEn,\n district: record.amphureNameTh,\n districtEn: record.amphureNameEn,\n postalCode: record.zipCode,\n }\n}\n","// NOTE (SZ-4): CJS consumers (`dist/index.cjs`) cannot tree-shake this hook out of the bundle.\n// If you are using the CJS build in a non-React environment, import only from the core entry\n// or use the ESM build with a bundler that supports tree-shaking.\nimport { useState, useEffect, useCallback, useRef } from 'react'\nimport { searchThaiAddress } from '../core/search'\nimport { formatThaiAddressSuggestion } from '../core/formatter'\nimport { resolveThaiAddress } from '../core/resolver'\nimport type {\n UseThaiAddressAutocompleteOptions,\n ThaiAddressRecord,\n ThaiAddressSuggestion,\n ResolvedThaiAddress,\n} from '../types'\n\nexport function useThaiAddressAutocomplete(options: UseThaiAddressAutocompleteOptions) {\n const { index, limit = 10, debounce: debounceMs = 200, threshold = 0.4 } = options\n\n const [query, setQueryState] = useState('')\n const [suggestions, setSuggestions] = useState<ThaiAddressSuggestion[]>([])\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const matchedRecordsMapRef = useRef<Map<string, ThaiAddressRecord>>(new Map())\n\n const setQuery = useCallback((value: string) => {\n setQueryState(value)\n }, [])\n\n useEffect(() => {\n if (timerRef.current) clearTimeout(timerRef.current)\n\n if (query.length === 0) {\n setSuggestions([])\n matchedRecordsMapRef.current = new Map()\n return\n }\n\n timerRef.current = setTimeout(() => {\n const records = searchThaiAddress(index, query, { limit, threshold })\n const formatted = records.map(formatThaiAddressSuggestion)\n const recordMap = new Map<string, ThaiAddressRecord>()\n for (let i = 0; i < records.length; i++) {\n recordMap.set(formatted[i].id, records[i])\n }\n matchedRecordsMapRef.current = recordMap\n setSuggestions(formatted)\n }, debounceMs)\n\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current)\n }\n }, [query, index, limit, threshold, debounceMs])\n\n /**\n * Resolves a suggestion to a full ThaiAddress and clears the suggestions list.\n *\n * Note: `query` is intentionally left unchanged so the input field retains the\n * typed text. Call `clear()` afterwards if you want to reset the input as well.\n */\n const selectSuggestion = useCallback(\n (item: ThaiAddressSuggestion): ResolvedThaiAddress => {\n const record = matchedRecordsMapRef.current.get(item.id)\n if (!record) {\n throw new Error(`[thaizip] No record found for suggestion id \"${item.id}\". Make sure to use suggestions returned by this hook.`)\n }\n setSuggestions([])\n matchedRecordsMapRef.current = new Map()\n return resolveThaiAddress(record)\n },\n [],\n )\n\n const clear = useCallback(() => {\n setQueryState('')\n setSuggestions([])\n matchedRecordsMapRef.current = new Map()\n }, [])\n\n return {\n query,\n setQuery,\n suggestions,\n isOpen: query.length > 0 && suggestions.length > 0,\n selectSuggestion,\n clear,\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/core/normalizer.ts","../src/core/trigrams.ts","../src/core/indexer.ts","../src/core/romanize.ts","../src/core/search.ts","../src/core/enumerate.ts","../src/core/formatter.ts","../src/core/resolver.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["const TONE_MARKS = /[่-๋็์]/g\nconst ADDRESS_PREFIXES = /^(จังหวัด|อำเภอ|ตำบล|แขวง|เขต|จ\\.|อ\\.|ต\\.|ข\\.)/\n\nexport function normalizeThaiAddressText(input: string): string {\n if (!input) return ''\n let text = input.trim()\n text = text.replace(ADDRESS_PREFIXES, '').trim()\n text = text.replace(TONE_MARKS, '')\n text = text.toLowerCase()\n return text\n}\n","import { normalizeThaiAddressText } from './normalizer'\n\n// Public: for callers who start from raw (un-normalised) text — e.g. indexer\nexport function extractTrigrams(text: string): Set<string> {\n return extractTrigramsNormalized(normalizeThaiAddressText(text))\n}\n\n// Internal: for callers who already hold a normalised string — e.g. search.ts\nexport function extractTrigramsNormalized(text: string): Set<string> {\n const result = new Set<string>()\n if (text.length === 0) return result\n if (text.length < 3) {\n result.add(text)\n return result\n }\n for (let i = 0; i <= text.length - 3; i++) {\n result.add(text.slice(i, i + 3))\n }\n return result\n}\n","import { extractTrigrams, extractTrigramsNormalized } from './trigrams'\nimport { normalizeThaiAddressText } from './normalizer'\nimport type { BuildIndexOptions, RawData, RawTambon, ThaiAddressRecord, TrigramIndex } from '../types'\n\nfunction typeErr(table: string, id: unknown, field: string, expected: string, value: unknown): TypeError {\n return new TypeError(`[thaizip] ${table} ${id}: expected ${expected} for ${field}, got ${typeof value}`)\n}\n\n/**\n * Validate that a `RawData` payload has the runtime shapes `buildThaiAddressIndex`\n * expects. Intended for consumers building an index from their own data (CSV, CMS,\n * private datasets) who want to fail fast with a descriptive error instead of an\n * opaque crash deep inside the normalizer, or silent `\"undefined\"` labels.\n *\n * Throws a `TypeError` (message prefixed `[thaizip]`) naming the table, the\n * offending record's `id`, the field, and the actual `typeof` on the first bad\n * field found. Does not filter soft-deleted rows first — validates every row as\n * provided.\n */\nexport function validateRawData(data: RawData): void {\n const { provinces, amphures, tambons } = data\n\n for (let i = 0; i < provinces.length; i++) {\n const p = provinces[i]\n if (typeof p.id !== 'number') throw typeErr('province', p.id, 'id', 'number', p.id)\n if (typeof p.name_th !== 'string') throw typeErr('province', p.id, 'name_th', 'string', p.name_th)\n if (typeof p.name_en !== 'string') throw typeErr('province', p.id, 'name_en', 'string', p.name_en)\n }\n\n for (let i = 0; i < amphures.length; i++) {\n const a = amphures[i]\n if (typeof a.id !== 'number') throw typeErr('amphure', a.id, 'id', 'number', a.id)\n if (typeof a.name_th !== 'string') throw typeErr('amphure', a.id, 'name_th', 'string', a.name_th)\n if (typeof a.name_en !== 'string') throw typeErr('amphure', a.id, 'name_en', 'string', a.name_en)\n if (typeof a.province_id !== 'number') throw typeErr('amphure', a.id, 'province_id', 'number', a.province_id)\n }\n\n for (let i = 0; i < tambons.length; i++) {\n const t = tambons[i]\n if (typeof t.id !== 'number') throw typeErr('tambon', t.id, 'id', 'number', t.id)\n if (typeof t.name_th !== 'string') throw typeErr('tambon', t.id, 'name_th', 'string', t.name_th)\n if (typeof t.name_en !== 'string') throw typeErr('tambon', t.id, 'name_en', 'string', t.name_en)\n const zipType = typeof t.zip_code\n if (zipType !== 'string' && zipType !== 'number') {\n throw typeErr('tambon', t.id, 'zip_code', 'string or number', t.zip_code)\n }\n if (typeof t.amphure_id !== 'number') throw typeErr('tambon', t.id, 'amphure_id', 'number', t.amphure_id)\n }\n}\n\nfunction addTrigrams(map: Map<string, Set<number>>, trigrams: Set<string>, idx: number): void {\n for (const trigram of trigrams) {\n let set = map.get(trigram)\n if (!set) {\n set = new Set()\n map.set(trigram, set)\n }\n set.add(idx)\n }\n}\n\nfunction combinedTrigrams(nameTh: string, nameEn: string): Set<string> {\n const s = new Set<string>()\n for (const t of extractTrigrams(nameTh)) s.add(t)\n for (const t of extractTrigrams(nameEn)) s.add(t)\n return s\n}\n\nexport function buildThaiAddressIndex(data: RawData, options?: BuildIndexOptions): TrigramIndex {\n if (options?.validate !== false) {\n validateRawData(data)\n }\n\n const { provinces, amphures, tambons } = data\n\n // Build lookup maps (filter deleted)\n const provMap = new Map(\n provinces.filter(p => !p.deleted_at).map(p => [p.id, p])\n )\n const ampMap = new Map(\n amphures.filter(a => !a.deleted_at).map(a => [a.id, a])\n )\n\n // Pre-compute trigrams for shared province and amphure fields to avoid\n // redundant normalization when multiple tambons share the same parent.\n const provTrigrams = new Map<number, Set<string>>()\n for (const [id, prov] of provMap) {\n provTrigrams.set(id, combinedTrigrams(prov.name_th, prov.name_en))\n }\n const ampTrigrams = new Map<number, Set<string>>()\n for (const [id, amp] of ampMap) {\n ampTrigrams.set(id, combinedTrigrams(amp.name_th, amp.name_en))\n }\n\n const records: ThaiAddressRecord[] = []\n const map = new Map<string, Set<number>>()\n const zipIndex = new Map<string, number[]>()\n const normTambon: string[] = []\n const normTambonEn: string[] = []\n const byProvince = new Map<number, number[]>()\n const byAmphure = new Map<number, number[]>()\n\n for (const tambon of tambons) {\n if (tambon.deleted_at) continue\n\n const amphure = ampMap.get(tambon.amphure_id)\n if (!amphure) {\n if (options?.onSkip) options.onSkip(tambon as RawTambon)\n continue\n }\n\n const province = provMap.get(amphure.province_id)\n if (!province) {\n if (options?.onSkip) options.onSkip(tambon as RawTambon)\n continue\n }\n\n const record: ThaiAddressRecord = {\n provinceId: province.id,\n provinceNameTh: province.name_th,\n provinceNameEn: province.name_en,\n amphureId: amphure.id,\n amphureNameTh: amphure.name_th,\n amphureNameEn: amphure.name_en,\n tambonId: tambon.id,\n tambonNameTh: tambon.name_th,\n tambonNameEn: tambon.name_en,\n zipCode: String(tambon.zip_code),\n }\n\n const idx = records.length\n records.push(record)\n\n // Build zip index\n const existing = zipIndex.get(record.zipCode)\n if (existing) existing.push(idx)\n else zipIndex.set(record.zipCode, [idx])\n\n // Parent groupings, for the enumeration API (cascade selects)\n const provList = byProvince.get(province.id)\n if (provList) provList.push(idx)\n else byProvince.set(province.id, [idx])\n const ampList = byAmphure.get(amphure.id)\n if (ampList) ampList.push(idx)\n else byAmphure.set(amphure.id, [idx])\n\n // Tambon-specific fields (unique per record). The normalized Thai name is\n // kept for the search ranker, and reused here so it is only computed once.\n const normTh = normalizeThaiAddressText(record.tambonNameTh)\n normTambon.push(normTh)\n addTrigrams(map, extractTrigramsNormalized(normTh), idx)\n const normEn = normalizeThaiAddressText(record.tambonNameEn)\n normTambonEn.push(normEn)\n addTrigrams(map, extractTrigramsNormalized(normEn), idx)\n addTrigrams(map, extractTrigrams(record.zipCode), idx)\n // Reuse pre-computed province and amphure trigrams\n addTrigrams(map, provTrigrams.get(province.id)!, idx)\n addTrigrams(map, ampTrigrams.get(amphure.id)!, idx)\n }\n\n return { map, records, zipIndex, normTambon, normTambonEn, byProvince, byAmphure }\n}\n","/**\n * Romanization alias expansion for Latin-script queries.\n *\n * The bundled dataset indexes only the official RTGS transliteration of each\n * name (`name_en`), so common but non-RTGS spellings a user actually types\n * (\"lardprao\", \"krungthep\", \"sukumvit\") miss the index entirely. This module\n * rewrites such a query into its RTGS form before trigram extraction.\n *\n * Contract:\n * - Input is an ALREADY-NORMALIZED query (lowercased, prefixes and tone marks\n * stripped) as produced by `normalizeThaiAddressText`.\n * - Output is a normalized query too — safe to hand straight to\n * `extractTrigramsNormalized`.\n * - Thai-script input must be returned unchanged.\n * - Must be a pure function; it runs on every keystroke.\n *\n * Design (two layers, cheapest/most-reliable first):\n *\n * 1. A curated dictionary of non-RTGS spelling -> exact RTGS string, as it\n * literally appears in a `name_en` field of the bundled dataset. Targets\n * were read off the real generated index (see the romanize benchmark\n * harness used to build this list), not guessed — a couple of entries\n * intentionally map onto data typos already baked into the dataset\n * (`loburi`, `buogkan`) because that is what is actually indexed.\n *\n * 2. A tiny, conservative cleanup pass used only when the dictionary misses:\n * collapse runs of whitespace, and strip a few English administrative\n * words (`district`, `province`, ...) that never occur inside a real\n * `name_en` value. Both were verified against the full dataset before\n * being added — see the \"rejected rules\" note below for transformations\n * that were tried and thrown out because they collide with real names.\n *\n * Rejected systematic rules (measured against the real dataset, not\n * hypothetical): a blanket `klong` -> `khlong` rewrite would break the real\n * tambons \"Mae Klong\" and \"Ho Klong\"; `muang` -> `mueang` would break ~122\n * real tambons that legitimately contain \"Muang\" as a name component (e.g.\n * \"Bang Muang\", \"Khok Muang\"); `huay` -> `huai` would break the real tambon\n * \"Huay Lueg\"; a blanket hyphen -> space rewrite would break the ~70 real\n * names that use a hyphen on purpose (e.g. \"Bang Pa-in\", the Ayutthaya\n * palace town). None of the classic AUA-vs-RTGS letter swaps (`oo` -> `u`,\n * trailing `-rd` -> `-t`, `dt` -> `t`, `bp` -> `p`, doubled consonants) had\n * any real occurrence in the traffic-prioritized query set used to validate\n * this module, so none were added — see the harness report for the\n * before/after numbers this file was tuned against.\n */\n\n// --- Layer 1: curated alias dictionary -------------------------------------\n//\n// Keys are already-normalized (lowercase) non-RTGS spellings. Values are the\n// exact RTGS substring as it appears in the dataset's `name_en` fields\n// (verified against the real generated index, not guessed).\nconst ALIASES: ReadonlyMap<string, string> = new Map([\n // --- Bangkok khet (districts) — concatenated (\"no space\") forms ---------\n ['phranakhon', 'phra nakhon'],\n ['nongchok', 'nong chok'],\n ['bangrak', 'bang rak'],\n ['bangkhen', 'bang khen'],\n ['bangkapi', 'bang kapi'],\n ['pathumwan', 'pathum wan'],\n ['patumwan', 'pathum wan'],\n ['pomprap', 'pom prap sattru phai'],\n ['phrakhanong', 'phra khanong'],\n ['minburi', 'min buri'],\n ['latkrabang', 'lat krabang'],\n ['ladkrabang', 'lat krabang'],\n ['yannawa', 'yan nawa'],\n ['phayathai', 'phaya thai'],\n ['thonburi', 'thon buri'],\n ['bangkokyai', 'bangkok yai'],\n ['huaikhwang', 'huai khwang'],\n ['huaykhwang', 'huai khwang'],\n ['huaykwang', 'huai khwang'],\n ['khlongsan', 'khlong san'],\n ['klongsan', 'khlong san'],\n ['talingchan', 'taling chan'],\n ['bangkoknoi', 'bangkok noi'],\n ['bangkhunthian', 'bang khun thian'],\n ['phasicharoen', 'phasi charoen'],\n ['nongkhaem', 'nong khaem'],\n ['ratburana', 'rat burana'],\n ['bangphlat', 'bang phlat'],\n ['dindaeng', 'din daeng'],\n ['buengkum', 'bueng kum'],\n ['bangsue', 'bang sue'],\n ['bangkholaem', 'bang kho laem'],\n ['khlongtoei', 'khlong toei'],\n ['klongtoei', 'khlong toei'],\n ['klongtoey', 'khlong toei'],\n ['khlongtoey', 'khlong toei'],\n ['suanluang', 'suan luang'],\n ['chomthong', 'chom thong'],\n ['donmueang', 'don mueang'],\n ['donmuang', 'don mueang'],\n ['latphrao', 'lat phrao'],\n ['ladprao', 'lat phrao'],\n ['lardprao', 'lat phrao'],\n ['lat prao', 'lat phrao'],\n ['bangkhae', 'bang khae'],\n ['laksi', 'lak si'],\n ['saimai', 'sai mai'],\n ['khannayao', 'khan na yao'],\n ['saphansung', 'saphan sung'],\n ['wangthonglang', 'wang thonglang'],\n ['khlongsamwa', 'khlong sam wa'],\n ['klongsamwa', 'khlong sam wa'],\n ['bangna', 'bang na'],\n ['thawiwatthana', 'thawi watthana'],\n ['thungkhru', 'thung khru'],\n ['bangbon', 'bang bon'],\n\n // --- Bangkok area / street nicknames with a common misspelling ----------\n ['sathorn', 'sathon'],\n ['wattana', 'watthana'],\n ['silom', 'si lom'],\n\n // --- Provinces and other high-traffic areas ------------------------------\n ['krungthep', 'bangkok'],\n ['korat', 'nakhon ratchasima'],\n ['nakornratchasima', 'nakhon ratchasima'],\n ['chiangmai', 'chiang mai'],\n ['chiangrai', 'chiang rai'],\n ['pathumthani', 'pathum thani'],\n ['patumthani', 'pathum thani'],\n ['samutprakan', 'samut prakan'],\n ['samutsakhon', 'samut sakhon'],\n ['samutsongkhram', 'samut songkhram'],\n ['nakhonpathom', 'nakhon pathom'],\n ['nakhonsawan', 'nakhon sawan'],\n ['nakhonsithammarat', 'nakhon si thammarat'],\n ['prachuapkhirikhan', 'prachuap khiri khan'],\n ['huahin', 'hua hin'],\n ['hatyai', 'hat yai'],\n ['hadyai', 'hat yai'],\n ['had yai', 'hat yai'],\n ['lopburi', 'loburi'],\n ['lop buri', 'loburi'],\n ['buengkan', 'buogkan'],\n ['bungkan', 'buogkan'],\n ['bung kan', 'buogkan'],\n ['udonthani', 'udon thani'],\n ['khonkaen', 'khon kaen'],\n ['suphanburi', 'suphan buri'],\n ['nongkhai', 'nong khai'],\n ['buriram', 'buri ram'],\n])\n\n// --- Layer 2: safe systematic cleanup (dictionary-miss fallback only) -----\n//\n// English administrative words that NEVER occur inside a real `name_en`\n// value (verified against the full dataset) — safe to strip unconditionally.\n// Deliberately excludes \"khet\" and \"tambon\": both occur as genuine name\n// components in the real data (\"Khet Chatuchak\" as a whole amphure name,\n// but also tambons like \"Sanam Chai Khet\" and \"Sam Tambon\"), so stripping\n// them would corrupt otherwise-correct queries.\nconst NOISE_WORDS_RE = /\\b(?:sub[- ]?district|district|province|changwat|amphoe|amphur)\\b/g\nconst WHITESPACE_RE = /\\s+/g\n\nfunction cleanup(text: string): string {\n const stripped = text.replace(NOISE_WORDS_RE, ' ').replace(WHITESPACE_RE, ' ').trim()\n return stripped.length > 0 ? stripped : text\n}\n\nexport function applyRomanizationAliases(normalized: string): string {\n if (!normalized) return normalized\n\n const direct = ALIASES.get(normalized)\n if (direct !== undefined) return direct\n\n const cleaned = cleanup(normalized)\n if (cleaned === normalized) return normalized\n\n const aliasedCleaned = ALIASES.get(cleaned)\n return aliasedCleaned !== undefined ? aliasedCleaned : cleaned\n}\n","import { normalizeThaiAddressText } from './normalizer'\nimport { extractTrigramsNormalized } from './trigrams'\nimport { applyRomanizationAliases } from './romanize'\nimport type { SearchOptions, ThaiAddressRecord, TrigramIndex } from '../types'\n\nconst ZIP_CODE_RE = /^\\d+$/\nconst HAS_LATIN_RE = /[a-z]/i\n\n// Constructing Intl.Collator is expensive; build it once at module load rather\n// than on every comparator invocation inside a hot sort loop.\nconst TH_COLLATOR = new Intl.Collator('th')\n\n/** Plain, allocation-free string comparison. Zip codes in the bundled dataset\n * are fixed-width zero-padded 5-digit strings, so lexical order already\n * matches numeric order — no need for `localeCompare`'s Intl machinery. */\nfunction compareZip(a: string, b: string): number {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n}\n\nfunction sortZipMatches(matches: ThaiAddressRecord[], exactZip: string): ThaiAddressRecord[] {\n matches.sort((a, b) => {\n if (a.zipCode === exactZip && b.zipCode !== exactZip) return -1\n if (b.zipCode === exactZip && a.zipCode !== exactZip) return 1\n return compareZip(a.zipCode, b.zipCode)\n })\n return matches\n}\n\n/**\n * Look up records by zip code (exact or prefix match). Exact matches are\n * returned before prefix matches; ties are ordered by ascending zip code.\n *\n * Unlike `searchThaiAddress`, results are NOT capped by `options.limit` —\n * only by `options.zipLimit` (defaults to `Infinity`), since a zip code can\n * legitimately map to dozens of tambons (e.g. `45000` → 33 records).\n */\nexport function lookupByZipCode(\n index: TrigramIndex,\n zip: string,\n options?: SearchOptions,\n): ThaiAddressRecord[] {\n if (!index || !zip) return []\n const normalized = zip.trim()\n if (!ZIP_CODE_RE.test(normalized) || normalized.length < 2) return []\n\n const zipLimit = options?.zipLimit ?? Infinity\n\n const matches: ThaiAddressRecord[] = []\n for (const [z, indices] of index.zipIndex) {\n if (z.startsWith(normalized)) {\n for (const idx of indices) matches.push(index.records[idx])\n }\n }\n sortZipMatches(matches, normalized)\n return matches.slice(0, zipLimit)\n}\n\nfunction rankAgainstName(name: string | undefined, query: string): number {\n if (name === undefined || name.length === 0) return 0\n if (name === query) return 3\n if (name.startsWith(query)) return 2\n if (name.includes(query)) return 1\n return 0\n}\n\n/** `3` = exact tambon name match, `2` = prefix match, `1` = substring match,\n * `0` = no match on the tambon's own (normalized) name. Used to rank a\n * record whose OWN name matches the query above a record that only matches\n * via its parent amphure/province.\n *\n * Both the Thai and the English name are checked: a Latin-script query can\n * never match the Thai name, so without the English side every romanized\n * query would fall back to alphabetical order and reproduce exactly the\n * ranking bug this function exists to fix. */\nfunction computeMatchRank(index: TrigramIndex, idx: number, query: string): number {\n const th = rankAgainstName(index.normTambon?.[idx], query)\n if (th === 3) return 3\n const en = rankAgainstName(index.normTambonEn?.[idx], query)\n return en > th ? en : th\n}\n\nexport function searchThaiAddress(\n index: TrigramIndex,\n query: string,\n options?: SearchOptions,\n): ThaiAddressRecord[] {\n const limit = options?.limit ?? 10\n const threshold = options?.threshold ?? 0.4\n\n if (!index || !query) return []\n // Bound the raw query before running the (multi-pass) normalizer over it.\n if (query.length > 1000) return []\n const normalized = normalizeThaiAddressText(query)\n if (normalized.length === 0) return []\n if (normalized.length > 300) return []\n\n // Special case: zip code query (require at least 2 digits to avoid overly broad matches)\n if (ZIP_CODE_RE.test(normalized)) {\n if (normalized.length < 2) return []\n return lookupByZipCode(index, normalized, {\n zipLimit: options?.zipLimit ?? Infinity,\n })\n }\n\n // Queries shorter than 3 chars produce a single-element trigram set where every\n // partial hit scores 1.0 — meaningless for text search. The index never contains\n // trigram keys shorter than 3 chars for names of length >= 3, so a sub-3-char\n // query can never usefully match anything.\n if (normalized.length < 3) return []\n\n // Expand common non-RTGS romanizations (e.g. \"lardprao\" -> \"lat phrao\") for\n // Latin-script queries. Thai-script queries are passed through untouched.\n const searchText =\n options?.romanizationAliases !== false && HAS_LATIN_RE.test(normalized)\n ? applyRomanizationAliases(normalized)\n : normalized\n\n const queryTrigrams = extractTrigramsNormalized(searchText)\n\n // Accumulate hit counts per record index\n const hits = new Map<number, number>()\n for (const trigram of queryTrigrams) {\n const candidates = index.map.get(trigram)\n if (!candidates) continue\n for (const idx of candidates) {\n hits.set(idx, (hits.get(idx) ?? 0) + 1)\n }\n }\n\n const scored: { idx: number; score: number; matchRank: number }[] = []\n for (const [idx, count] of hits) {\n const score = count / queryTrigrams.size\n if (score >= threshold) {\n scored.push({ idx, score, matchRank: computeMatchRank(index, idx, searchText) })\n }\n }\n\n // Cheap numeric pre-sort (score, then matchRank) — no collator, no string\n // comparisons. This must include matchRank, not just score: an exact-name\n // match that ties on score with many parent-only matches could otherwise\n // fall outside the re-sorted window below and be lost.\n scored.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score\n return b.matchRank - a.matchRank\n })\n\n // Only the top window is worth the expense of a locale-aware tie-break;\n // re-sort just that slice with the full comparator (including the\n // module-level cached collator) before truncating to `limit`.\n const windowSize = Math.max(limit * 4, 50)\n const window = scored.slice(0, windowSize)\n window.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score\n if (b.matchRank !== a.matchRank) return b.matchRank - a.matchRank\n const ra = index.records[a.idx]\n const rb = index.records[b.idx]\n return (\n TH_COLLATOR.compare(ra.provinceNameTh, rb.provinceNameTh) ||\n TH_COLLATOR.compare(ra.amphureNameTh, rb.amphureNameTh) ||\n TH_COLLATOR.compare(ra.tambonNameTh, rb.tambonNameTh)\n )\n })\n\n return window.slice(0, limit).map(({ idx }) => index.records[idx])\n}\n","import type { AmphureSummary, ProvinceSummary, TambonSummary, ThaiAddressRecord, TrigramIndex } from '../types'\n\n// Sorting Thai names inside a comparator must never construct a new Collator\n// or call `localeCompare` with a locale argument per-call — that is the exact\n// PERF-1 hot-path bug fixed elsewhere in this codebase. One cached instance.\nconst thCollator = new Intl.Collator('th')\n\nfunction byNameTh(a: { nameTh: string }, b: { nameTh: string }): number {\n return thCollator.compare(a.nameTh, b.nameTh)\n}\n\n/**\n * All provinces present in the index, deduplicated and sorted by Thai name.\n * Backed by `index.byProvince` (O(unique provinces)); falls back to scanning\n * `index.records` if an older-shaped index lacks that map.\n */\nexport function listProvinces(index: TrigramIndex): ProvinceSummary[] {\n const seen = new Map<number, ProvinceSummary>()\n\n if (index.byProvince) {\n for (const [provinceId, recordIndices] of index.byProvince) {\n if (seen.has(provinceId) || recordIndices.length === 0) continue\n const r = index.records[recordIndices[0]]\n seen.set(provinceId, { id: provinceId, nameTh: r.provinceNameTh, nameEn: r.provinceNameEn })\n }\n } else {\n for (const r of index.records) {\n if (!seen.has(r.provinceId)) {\n seen.set(r.provinceId, { id: r.provinceId, nameTh: r.provinceNameTh, nameEn: r.provinceNameEn })\n }\n }\n }\n\n return Array.from(seen.values()).sort(byNameTh)\n}\n\n/**\n * Amphures (districts) within a given province, deduplicated and sorted by\n * Thai name. Unknown `provinceId` returns `[]`. Backed by `index.byProvince`;\n * falls back to scanning `index.records` if an older-shaped index lacks it.\n */\nexport function listAmphures(index: TrigramIndex, provinceId: number): AmphureSummary[] {\n const seen = new Map<number, AmphureSummary>()\n\n const addFrom = (r: ThaiAddressRecord): void => {\n if (!seen.has(r.amphureId)) {\n seen.set(r.amphureId, {\n id: r.amphureId,\n nameTh: r.amphureNameTh,\n nameEn: r.amphureNameEn,\n provinceId: r.provinceId,\n })\n }\n }\n\n if (index.byProvince) {\n const recordIndices = index.byProvince.get(provinceId)\n if (!recordIndices) return []\n for (const idx of recordIndices) addFrom(index.records[idx])\n } else {\n for (const r of index.records) {\n if (r.provinceId === provinceId) addFrom(r)\n }\n }\n\n return Array.from(seen.values()).sort(byNameTh)\n}\n\n/**\n * Tambons (subdistricts) within a given amphure, sorted by Thai name.\n * Unknown `amphureId` returns `[]`. Backed by `index.byAmphure`; falls back\n * to scanning `index.records` if an older-shaped index lacks it. Each\n * `byAmphure` entry already maps to exactly one tambon record, so no\n * deduplication is needed here.\n */\nexport function listTambons(index: TrigramIndex, amphureId: number): TambonSummary[] {\n const toSummary = (r: ThaiAddressRecord): TambonSummary => ({\n id: r.tambonId,\n nameTh: r.tambonNameTh,\n nameEn: r.tambonNameEn,\n amphureId: r.amphureId,\n zipCode: r.zipCode,\n })\n\n let result: TambonSummary[]\n\n if (index.byAmphure) {\n const recordIndices = index.byAmphure.get(amphureId)\n if (!recordIndices) return []\n result = recordIndices.map(idx => toSummary(index.records[idx]))\n } else {\n result = index.records.filter(r => r.amphureId === amphureId).map(toSummary)\n }\n\n return result.sort(byNameTh)\n}\n","import type {\n FormatSuggestionOptions,\n ThaiAddressRecord,\n ThaiAddressSuggestion,\n} from '../types'\n\nexport function formatThaiAddressSuggestion(\n record: ThaiAddressRecord,\n options?: FormatSuggestionOptions,\n): ThaiAddressSuggestion {\n const labelTh = `${record.tambonNameTh} > ${record.amphureNameTh} > ${record.provinceNameTh} ${record.zipCode}`\n const labelEn = `${record.tambonNameEn} > ${record.amphureNameEn} > ${record.provinceNameEn} ${record.zipCode}`\n\n return {\n id: String(record.tambonId),\n label: options?.locale === 'en' ? labelEn : labelTh,\n labelTh,\n labelEn,\n tambon: record.tambonNameTh,\n tambonEn: record.tambonNameEn,\n amphure: record.amphureNameTh,\n amphureEn: record.amphureNameEn,\n province: record.provinceNameTh,\n provinceEn: record.provinceNameEn,\n zipCode: record.zipCode,\n }\n}\n","import type { ThaiAddressRecord, ResolvedThaiAddress } from '../types'\n\nexport function resolveThaiAddress(record: ThaiAddressRecord): ResolvedThaiAddress {\n return {\n tambon: record.tambonNameTh,\n tambonEn: record.tambonNameEn,\n amphure: record.amphureNameTh,\n amphureEn: record.amphureNameEn,\n province: record.provinceNameTh,\n provinceEn: record.provinceNameEn,\n zipCode: record.zipCode,\n subdistrict: record.tambonNameTh,\n subdistrictEn: record.tambonNameEn,\n district: record.amphureNameTh,\n districtEn: record.amphureNameEn,\n postalCode: record.zipCode,\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -14,10 +14,37 @@ type TrigramIndex = {
14
14
  map: Map<string, Set<number>>;
15
15
  records: ThaiAddressRecord[];
16
16
  zipIndex: Map<string, number[]>;
17
+ /**
18
+ * Normalized Thai tambon names, parallel to `records` (same index).
19
+ * Used by the search ranker for exact/prefix match detection without
20
+ * re-normalizing on every query.
21
+ */
22
+ normTambon: string[];
23
+ /**
24
+ * Normalized English (RTGS) tambon names, parallel to `records`. Needed so
25
+ * romanized queries get the same own-name-beats-parent-name ranking that
26
+ * Thai queries get.
27
+ */
28
+ normTambonEn: string[];
29
+ /** Record indices grouped by `provinceId`, in insertion order. */
30
+ byProvince: Map<number, number[]>;
31
+ /** Record indices grouped by `amphureId`, in insertion order. */
32
+ byAmphure: Map<number, number[]>;
33
+ };
34
+ /** Which language a formatted label should be rendered in. */
35
+ type AddressLocale = 'th' | 'en';
36
+ type FormatSuggestionOptions = {
37
+ /** Language for the `label` field. Defaults to `'th'`. */
38
+ locale?: AddressLocale;
17
39
  };
18
40
  type ThaiAddressSuggestion = {
19
41
  id: string;
42
+ /** Human-readable label in the requested locale (Thai by default). */
20
43
  label: string;
44
+ /** Human-readable label in Thai, regardless of the requested locale. */
45
+ labelTh: string;
46
+ /** Human-readable label in English, regardless of the requested locale. */
47
+ labelEn: string;
21
48
  tambon: string;
22
49
  tambonEn: string;
23
50
  amphure: string;
@@ -26,6 +53,27 @@ type ThaiAddressSuggestion = {
26
53
  provinceEn: string;
27
54
  zipCode: string;
28
55
  };
56
+ /** A province entry returned by the enumeration API. */
57
+ type ProvinceSummary = {
58
+ id: number;
59
+ nameTh: string;
60
+ nameEn: string;
61
+ };
62
+ /** An amphure (district) entry returned by the enumeration API. */
63
+ type AmphureSummary = {
64
+ id: number;
65
+ nameTh: string;
66
+ nameEn: string;
67
+ provinceId: number;
68
+ };
69
+ /** A tambon (subdistrict) entry returned by the enumeration API. */
70
+ type TambonSummary = {
71
+ id: number;
72
+ nameTh: string;
73
+ nameEn: string;
74
+ amphureId: number;
75
+ zipCode: string;
76
+ };
29
77
  type ResolvedThaiAddress = {
30
78
  tambon: string;
31
79
  tambonEn: string;
@@ -43,12 +91,17 @@ type ResolvedThaiAddress = {
43
91
  type SearchOptions = {
44
92
  limit?: number;
45
93
  threshold?: number;
46
- };
47
- type UseThaiAddressAutocompleteOptions = {
48
- index: TrigramIndex;
49
- limit?: number;
50
- debounce?: number;
51
- threshold?: number;
94
+ /**
95
+ * Maximum results for all-digit (zip code) queries. A zip code maps to at most
96
+ * a few dozen tambons, so this defaults to `Infinity` — capping zip results at
97
+ * the text-autocomplete `limit` silently hides valid subdistricts.
98
+ */
99
+ zipLimit?: number;
100
+ /**
101
+ * Expand common romanization variants (e.g. `lardprao` → `lat phrao`) before
102
+ * searching. Only affects Latin-script queries. Defaults to `true`.
103
+ */
104
+ romanizationAliases?: boolean;
52
105
  };
53
106
  type RawGeography = {
54
107
  id: number;
@@ -71,12 +124,21 @@ type RawAmphure = {
71
124
  };
72
125
  type RawTambon = {
73
126
  id: number;
74
- zip_code: number;
127
+ zip_code: number | string;
75
128
  name_th: string;
76
129
  name_en: string;
77
130
  amphure_id: number;
78
131
  deleted_at: string | null;
79
132
  };
133
+ type BuildIndexOptions = {
134
+ onSkip?: (tambon: RawTambon) => void;
135
+ /**
136
+ * Validate that every consumed field has the expected runtime type and throw a
137
+ * descriptive `TypeError` if not. Defaults to `true`; set to `false` to skip
138
+ * the checks when the data source is already trusted (e.g. the bundled index).
139
+ */
140
+ validate?: boolean;
141
+ };
80
142
  type RawData = {
81
143
  geographies?: RawGeography[];
82
144
  provinces: RawProvince[];
@@ -84,25 +146,103 @@ type RawData = {
84
146
  tambons: RawTambon[];
85
147
  };
86
148
 
87
- declare function buildThaiAddressIndex(data: RawData): TrigramIndex;
88
-
89
- declare function clearDefaultIndex(): void;
149
+ /**
150
+ * Validate that a `RawData` payload has the runtime shapes `buildThaiAddressIndex`
151
+ * expects. Intended for consumers building an index from their own data (CSV, CMS,
152
+ * private datasets) who want to fail fast with a descriptive error instead of an
153
+ * opaque crash deep inside the normalizer, or silent `"undefined"` labels.
154
+ *
155
+ * Throws a `TypeError` (message prefixed `[thaizip]`) naming the table, the
156
+ * offending record's `id`, the field, and the actual `typeof` on the first bad
157
+ * field found. Does not filter soft-deleted rows first — validates every row as
158
+ * provided.
159
+ */
160
+ declare function validateRawData(data: RawData): void;
161
+ declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOptions): TrigramIndex;
90
162
 
163
+ /**
164
+ * Look up records by zip code (exact or prefix match). Exact matches are
165
+ * returned before prefix matches; ties are ordered by ascending zip code.
166
+ *
167
+ * Unlike `searchThaiAddress`, results are NOT capped by `options.limit` —
168
+ * only by `options.zipLimit` (defaults to `Infinity`), since a zip code can
169
+ * legitimately map to dozens of tambons (e.g. `45000` → 33 records).
170
+ */
171
+ declare function lookupByZipCode(index: TrigramIndex, zip: string, options?: SearchOptions): ThaiAddressRecord[];
91
172
  declare function searchThaiAddress(index: TrigramIndex, query: string, options?: SearchOptions): ThaiAddressRecord[];
92
173
 
93
- declare function formatThaiAddressSuggestion(record: ThaiAddressRecord): ThaiAddressSuggestion;
174
+ /**
175
+ * All provinces present in the index, deduplicated and sorted by Thai name.
176
+ * Backed by `index.byProvince` (O(unique provinces)); falls back to scanning
177
+ * `index.records` if an older-shaped index lacks that map.
178
+ */
179
+ declare function listProvinces(index: TrigramIndex): ProvinceSummary[];
180
+ /**
181
+ * Amphures (districts) within a given province, deduplicated and sorted by
182
+ * Thai name. Unknown `provinceId` returns `[]`. Backed by `index.byProvince`;
183
+ * falls back to scanning `index.records` if an older-shaped index lacks it.
184
+ */
185
+ declare function listAmphures(index: TrigramIndex, provinceId: number): AmphureSummary[];
186
+ /**
187
+ * Tambons (subdistricts) within a given amphure, sorted by Thai name.
188
+ * Unknown `amphureId` returns `[]`. Backed by `index.byAmphure`; falls back
189
+ * to scanning `index.records` if an older-shaped index lacks it. Each
190
+ * `byAmphure` entry already maps to exactly one tambon record, so no
191
+ * deduplication is needed here.
192
+ */
193
+ declare function listTambons(index: TrigramIndex, amphureId: number): TambonSummary[];
194
+
195
+ declare function formatThaiAddressSuggestion(record: ThaiAddressRecord, options?: FormatSuggestionOptions): ThaiAddressSuggestion;
94
196
 
95
197
  declare function resolveThaiAddress(record: ThaiAddressRecord): ResolvedThaiAddress;
96
198
 
97
199
  declare function normalizeThaiAddressText(input: string): string;
98
200
 
99
- declare function useThaiAddressAutocomplete(options: UseThaiAddressAutocompleteOptions): {
100
- query: string;
101
- setQuery: (value: string) => void;
102
- suggestions: ThaiAddressSuggestion[];
103
- isOpen: boolean;
104
- selectSuggestion: (item: ThaiAddressSuggestion) => ResolvedThaiAddress;
105
- clear: () => void;
106
- };
201
+ /**
202
+ * Romanization alias expansion for Latin-script queries.
203
+ *
204
+ * The bundled dataset indexes only the official RTGS transliteration of each
205
+ * name (`name_en`), so common but non-RTGS spellings a user actually types
206
+ * ("lardprao", "krungthep", "sukumvit") miss the index entirely. This module
207
+ * rewrites such a query into its RTGS form before trigram extraction.
208
+ *
209
+ * Contract:
210
+ * - Input is an ALREADY-NORMALIZED query (lowercased, prefixes and tone marks
211
+ * stripped) as produced by `normalizeThaiAddressText`.
212
+ * - Output is a normalized query too — safe to hand straight to
213
+ * `extractTrigramsNormalized`.
214
+ * - Thai-script input must be returned unchanged.
215
+ * - Must be a pure function; it runs on every keystroke.
216
+ *
217
+ * Design (two layers, cheapest/most-reliable first):
218
+ *
219
+ * 1. A curated dictionary of non-RTGS spelling -> exact RTGS string, as it
220
+ * literally appears in a `name_en` field of the bundled dataset. Targets
221
+ * were read off the real generated index (see the romanize benchmark
222
+ * harness used to build this list), not guessed — a couple of entries
223
+ * intentionally map onto data typos already baked into the dataset
224
+ * (`loburi`, `buogkan`) because that is what is actually indexed.
225
+ *
226
+ * 2. A tiny, conservative cleanup pass used only when the dictionary misses:
227
+ * collapse runs of whitespace, and strip a few English administrative
228
+ * words (`district`, `province`, ...) that never occur inside a real
229
+ * `name_en` value. Both were verified against the full dataset before
230
+ * being added — see the "rejected rules" note below for transformations
231
+ * that were tried and thrown out because they collide with real names.
232
+ *
233
+ * Rejected systematic rules (measured against the real dataset, not
234
+ * hypothetical): a blanket `klong` -> `khlong` rewrite would break the real
235
+ * tambons "Mae Klong" and "Ho Klong"; `muang` -> `mueang` would break ~122
236
+ * real tambons that legitimately contain "Muang" as a name component (e.g.
237
+ * "Bang Muang", "Khok Muang"); `huay` -> `huai` would break the real tambon
238
+ * "Huay Lueg"; a blanket hyphen -> space rewrite would break the ~70 real
239
+ * names that use a hyphen on purpose (e.g. "Bang Pa-in", the Ayutthaya
240
+ * palace town). None of the classic AUA-vs-RTGS letter swaps (`oo` -> `u`,
241
+ * trailing `-rd` -> `-t`, `dt` -> `t`, `bp` -> `p`, doubled consonants) had
242
+ * any real occurrence in the traffic-prioritized query set used to validate
243
+ * this module, so none were added — see the harness report for the
244
+ * before/after numbers this file was tuned against.
245
+ */
246
+ declare function applyRomanizationAliases(normalized: string): string;
107
247
 
108
- export { type RawAmphure, type RawData, type RawGeography, type RawProvince, type RawTambon, type ResolvedThaiAddress, type SearchOptions, type ThaiAddressRecord, type ThaiAddressSuggestion, type TrigramIndex, type UseThaiAddressAutocompleteOptions, buildThaiAddressIndex, clearDefaultIndex, formatThaiAddressSuggestion, normalizeThaiAddressText, resolveThaiAddress, searchThaiAddress, useThaiAddressAutocomplete };
248
+ export { type AddressLocale, type AmphureSummary, type BuildIndexOptions, type FormatSuggestionOptions, type ProvinceSummary, type RawAmphure, type RawData, type RawGeography, type RawProvince, type RawTambon, type ResolvedThaiAddress, type SearchOptions, type TambonSummary, type ThaiAddressRecord, type ThaiAddressSuggestion, type TrigramIndex, applyRomanizationAliases, buildThaiAddressIndex, formatThaiAddressSuggestion, listAmphures, listProvinces, listTambons, lookupByZipCode, normalizeThaiAddressText, resolveThaiAddress, searchThaiAddress, validateRawData };
package/dist/index.d.ts CHANGED
@@ -14,10 +14,37 @@ type TrigramIndex = {
14
14
  map: Map<string, Set<number>>;
15
15
  records: ThaiAddressRecord[];
16
16
  zipIndex: Map<string, number[]>;
17
+ /**
18
+ * Normalized Thai tambon names, parallel to `records` (same index).
19
+ * Used by the search ranker for exact/prefix match detection without
20
+ * re-normalizing on every query.
21
+ */
22
+ normTambon: string[];
23
+ /**
24
+ * Normalized English (RTGS) tambon names, parallel to `records`. Needed so
25
+ * romanized queries get the same own-name-beats-parent-name ranking that
26
+ * Thai queries get.
27
+ */
28
+ normTambonEn: string[];
29
+ /** Record indices grouped by `provinceId`, in insertion order. */
30
+ byProvince: Map<number, number[]>;
31
+ /** Record indices grouped by `amphureId`, in insertion order. */
32
+ byAmphure: Map<number, number[]>;
33
+ };
34
+ /** Which language a formatted label should be rendered in. */
35
+ type AddressLocale = 'th' | 'en';
36
+ type FormatSuggestionOptions = {
37
+ /** Language for the `label` field. Defaults to `'th'`. */
38
+ locale?: AddressLocale;
17
39
  };
18
40
  type ThaiAddressSuggestion = {
19
41
  id: string;
42
+ /** Human-readable label in the requested locale (Thai by default). */
20
43
  label: string;
44
+ /** Human-readable label in Thai, regardless of the requested locale. */
45
+ labelTh: string;
46
+ /** Human-readable label in English, regardless of the requested locale. */
47
+ labelEn: string;
21
48
  tambon: string;
22
49
  tambonEn: string;
23
50
  amphure: string;
@@ -26,6 +53,27 @@ type ThaiAddressSuggestion = {
26
53
  provinceEn: string;
27
54
  zipCode: string;
28
55
  };
56
+ /** A province entry returned by the enumeration API. */
57
+ type ProvinceSummary = {
58
+ id: number;
59
+ nameTh: string;
60
+ nameEn: string;
61
+ };
62
+ /** An amphure (district) entry returned by the enumeration API. */
63
+ type AmphureSummary = {
64
+ id: number;
65
+ nameTh: string;
66
+ nameEn: string;
67
+ provinceId: number;
68
+ };
69
+ /** A tambon (subdistrict) entry returned by the enumeration API. */
70
+ type TambonSummary = {
71
+ id: number;
72
+ nameTh: string;
73
+ nameEn: string;
74
+ amphureId: number;
75
+ zipCode: string;
76
+ };
29
77
  type ResolvedThaiAddress = {
30
78
  tambon: string;
31
79
  tambonEn: string;
@@ -43,12 +91,17 @@ type ResolvedThaiAddress = {
43
91
  type SearchOptions = {
44
92
  limit?: number;
45
93
  threshold?: number;
46
- };
47
- type UseThaiAddressAutocompleteOptions = {
48
- index: TrigramIndex;
49
- limit?: number;
50
- debounce?: number;
51
- threshold?: number;
94
+ /**
95
+ * Maximum results for all-digit (zip code) queries. A zip code maps to at most
96
+ * a few dozen tambons, so this defaults to `Infinity` — capping zip results at
97
+ * the text-autocomplete `limit` silently hides valid subdistricts.
98
+ */
99
+ zipLimit?: number;
100
+ /**
101
+ * Expand common romanization variants (e.g. `lardprao` → `lat phrao`) before
102
+ * searching. Only affects Latin-script queries. Defaults to `true`.
103
+ */
104
+ romanizationAliases?: boolean;
52
105
  };
53
106
  type RawGeography = {
54
107
  id: number;
@@ -71,12 +124,21 @@ type RawAmphure = {
71
124
  };
72
125
  type RawTambon = {
73
126
  id: number;
74
- zip_code: number;
127
+ zip_code: number | string;
75
128
  name_th: string;
76
129
  name_en: string;
77
130
  amphure_id: number;
78
131
  deleted_at: string | null;
79
132
  };
133
+ type BuildIndexOptions = {
134
+ onSkip?: (tambon: RawTambon) => void;
135
+ /**
136
+ * Validate that every consumed field has the expected runtime type and throw a
137
+ * descriptive `TypeError` if not. Defaults to `true`; set to `false` to skip
138
+ * the checks when the data source is already trusted (e.g. the bundled index).
139
+ */
140
+ validate?: boolean;
141
+ };
80
142
  type RawData = {
81
143
  geographies?: RawGeography[];
82
144
  provinces: RawProvince[];
@@ -84,25 +146,103 @@ type RawData = {
84
146
  tambons: RawTambon[];
85
147
  };
86
148
 
87
- declare function buildThaiAddressIndex(data: RawData): TrigramIndex;
88
-
89
- declare function clearDefaultIndex(): void;
149
+ /**
150
+ * Validate that a `RawData` payload has the runtime shapes `buildThaiAddressIndex`
151
+ * expects. Intended for consumers building an index from their own data (CSV, CMS,
152
+ * private datasets) who want to fail fast with a descriptive error instead of an
153
+ * opaque crash deep inside the normalizer, or silent `"undefined"` labels.
154
+ *
155
+ * Throws a `TypeError` (message prefixed `[thaizip]`) naming the table, the
156
+ * offending record's `id`, the field, and the actual `typeof` on the first bad
157
+ * field found. Does not filter soft-deleted rows first — validates every row as
158
+ * provided.
159
+ */
160
+ declare function validateRawData(data: RawData): void;
161
+ declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOptions): TrigramIndex;
90
162
 
163
+ /**
164
+ * Look up records by zip code (exact or prefix match). Exact matches are
165
+ * returned before prefix matches; ties are ordered by ascending zip code.
166
+ *
167
+ * Unlike `searchThaiAddress`, results are NOT capped by `options.limit` —
168
+ * only by `options.zipLimit` (defaults to `Infinity`), since a zip code can
169
+ * legitimately map to dozens of tambons (e.g. `45000` → 33 records).
170
+ */
171
+ declare function lookupByZipCode(index: TrigramIndex, zip: string, options?: SearchOptions): ThaiAddressRecord[];
91
172
  declare function searchThaiAddress(index: TrigramIndex, query: string, options?: SearchOptions): ThaiAddressRecord[];
92
173
 
93
- declare function formatThaiAddressSuggestion(record: ThaiAddressRecord): ThaiAddressSuggestion;
174
+ /**
175
+ * All provinces present in the index, deduplicated and sorted by Thai name.
176
+ * Backed by `index.byProvince` (O(unique provinces)); falls back to scanning
177
+ * `index.records` if an older-shaped index lacks that map.
178
+ */
179
+ declare function listProvinces(index: TrigramIndex): ProvinceSummary[];
180
+ /**
181
+ * Amphures (districts) within a given province, deduplicated and sorted by
182
+ * Thai name. Unknown `provinceId` returns `[]`. Backed by `index.byProvince`;
183
+ * falls back to scanning `index.records` if an older-shaped index lacks it.
184
+ */
185
+ declare function listAmphures(index: TrigramIndex, provinceId: number): AmphureSummary[];
186
+ /**
187
+ * Tambons (subdistricts) within a given amphure, sorted by Thai name.
188
+ * Unknown `amphureId` returns `[]`. Backed by `index.byAmphure`; falls back
189
+ * to scanning `index.records` if an older-shaped index lacks it. Each
190
+ * `byAmphure` entry already maps to exactly one tambon record, so no
191
+ * deduplication is needed here.
192
+ */
193
+ declare function listTambons(index: TrigramIndex, amphureId: number): TambonSummary[];
194
+
195
+ declare function formatThaiAddressSuggestion(record: ThaiAddressRecord, options?: FormatSuggestionOptions): ThaiAddressSuggestion;
94
196
 
95
197
  declare function resolveThaiAddress(record: ThaiAddressRecord): ResolvedThaiAddress;
96
198
 
97
199
  declare function normalizeThaiAddressText(input: string): string;
98
200
 
99
- declare function useThaiAddressAutocomplete(options: UseThaiAddressAutocompleteOptions): {
100
- query: string;
101
- setQuery: (value: string) => void;
102
- suggestions: ThaiAddressSuggestion[];
103
- isOpen: boolean;
104
- selectSuggestion: (item: ThaiAddressSuggestion) => ResolvedThaiAddress;
105
- clear: () => void;
106
- };
201
+ /**
202
+ * Romanization alias expansion for Latin-script queries.
203
+ *
204
+ * The bundled dataset indexes only the official RTGS transliteration of each
205
+ * name (`name_en`), so common but non-RTGS spellings a user actually types
206
+ * ("lardprao", "krungthep", "sukumvit") miss the index entirely. This module
207
+ * rewrites such a query into its RTGS form before trigram extraction.
208
+ *
209
+ * Contract:
210
+ * - Input is an ALREADY-NORMALIZED query (lowercased, prefixes and tone marks
211
+ * stripped) as produced by `normalizeThaiAddressText`.
212
+ * - Output is a normalized query too — safe to hand straight to
213
+ * `extractTrigramsNormalized`.
214
+ * - Thai-script input must be returned unchanged.
215
+ * - Must be a pure function; it runs on every keystroke.
216
+ *
217
+ * Design (two layers, cheapest/most-reliable first):
218
+ *
219
+ * 1. A curated dictionary of non-RTGS spelling -> exact RTGS string, as it
220
+ * literally appears in a `name_en` field of the bundled dataset. Targets
221
+ * were read off the real generated index (see the romanize benchmark
222
+ * harness used to build this list), not guessed — a couple of entries
223
+ * intentionally map onto data typos already baked into the dataset
224
+ * (`loburi`, `buogkan`) because that is what is actually indexed.
225
+ *
226
+ * 2. A tiny, conservative cleanup pass used only when the dictionary misses:
227
+ * collapse runs of whitespace, and strip a few English administrative
228
+ * words (`district`, `province`, ...) that never occur inside a real
229
+ * `name_en` value. Both were verified against the full dataset before
230
+ * being added — see the "rejected rules" note below for transformations
231
+ * that were tried and thrown out because they collide with real names.
232
+ *
233
+ * Rejected systematic rules (measured against the real dataset, not
234
+ * hypothetical): a blanket `klong` -> `khlong` rewrite would break the real
235
+ * tambons "Mae Klong" and "Ho Klong"; `muang` -> `mueang` would break ~122
236
+ * real tambons that legitimately contain "Muang" as a name component (e.g.
237
+ * "Bang Muang", "Khok Muang"); `huay` -> `huai` would break the real tambon
238
+ * "Huay Lueg"; a blanket hyphen -> space rewrite would break the ~70 real
239
+ * names that use a hyphen on purpose (e.g. "Bang Pa-in", the Ayutthaya
240
+ * palace town). None of the classic AUA-vs-RTGS letter swaps (`oo` -> `u`,
241
+ * trailing `-rd` -> `-t`, `dt` -> `t`, `bp` -> `p`, doubled consonants) had
242
+ * any real occurrence in the traffic-prioritized query set used to validate
243
+ * this module, so none were added — see the harness report for the
244
+ * before/after numbers this file was tuned against.
245
+ */
246
+ declare function applyRomanizationAliases(normalized: string): string;
107
247
 
108
- export { type RawAmphure, type RawData, type RawGeography, type RawProvince, type RawTambon, type ResolvedThaiAddress, type SearchOptions, type ThaiAddressRecord, type ThaiAddressSuggestion, type TrigramIndex, type UseThaiAddressAutocompleteOptions, buildThaiAddressIndex, clearDefaultIndex, formatThaiAddressSuggestion, normalizeThaiAddressText, resolveThaiAddress, searchThaiAddress, useThaiAddressAutocomplete };
248
+ export { type AddressLocale, type AmphureSummary, type BuildIndexOptions, type FormatSuggestionOptions, type ProvinceSummary, type RawAmphure, type RawData, type RawGeography, type RawProvince, type RawTambon, type ResolvedThaiAddress, type SearchOptions, type TambonSummary, type ThaiAddressRecord, type ThaiAddressSuggestion, type TrigramIndex, applyRomanizationAliases, buildThaiAddressIndex, formatThaiAddressSuggestion, listAmphures, listProvinces, listTambons, lookupByZipCode, normalizeThaiAddressText, resolveThaiAddress, searchThaiAddress, validateRawData };