thaizip 0.6.0 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -14,10 +14,46 @@ type TrigramIndex = {
14
14
  map: Map<string, Set<number>>;
15
15
  records: ThaiAddressRecord[];
16
16
  zipIndex: Map<string, number[]>;
17
+ /**
18
+ * Zip codes of `zipIndex` in ascending lexical order, with postings
19
+ * parallel in `sortedZipPostings`. Used by `lookupByZipCode` for
20
+ * O(log n + matches) prefix lookup instead of scanning every zip code.
21
+ * Optional: older-shaped or hand-built indexes without these fields fall
22
+ * back to the O(n_zips) scan.
23
+ */
24
+ sortedZipKeys?: string[];
25
+ sortedZipPostings?: number[][];
26
+ /**
27
+ * Normalized Thai tambon names, parallel to `records` (same index).
28
+ * Used by the search ranker for exact/prefix match detection without
29
+ * re-normalizing on every query.
30
+ */
31
+ normTambon: string[];
32
+ /**
33
+ * Normalized English (RTGS) tambon names, parallel to `records`. Needed so
34
+ * romanized queries get the same own-name-beats-parent-name ranking that
35
+ * Thai queries get.
36
+ */
37
+ normTambonEn: string[];
38
+ /** Record indices grouped by `provinceId`, in insertion order. */
39
+ byProvince: Map<number, number[]>;
40
+ /** Record indices grouped by `amphureId`, in insertion order. */
41
+ byAmphure: Map<number, number[]>;
42
+ };
43
+ /** Which language a formatted label should be rendered in. */
44
+ type AddressLocale = 'th' | 'en';
45
+ type FormatSuggestionOptions = {
46
+ /** Language for the `label` field. Defaults to `'th'`. */
47
+ locale?: AddressLocale;
17
48
  };
18
49
  type ThaiAddressSuggestion = {
19
50
  id: string;
51
+ /** Human-readable label in the requested locale (Thai by default). */
20
52
  label: string;
53
+ /** Human-readable label in Thai, regardless of the requested locale. */
54
+ labelTh: string;
55
+ /** Human-readable label in English, regardless of the requested locale. */
56
+ labelEn: string;
21
57
  tambon: string;
22
58
  tambonEn: string;
23
59
  amphure: string;
@@ -26,6 +62,27 @@ type ThaiAddressSuggestion = {
26
62
  provinceEn: string;
27
63
  zipCode: string;
28
64
  };
65
+ /** A province entry returned by the enumeration API. */
66
+ type ProvinceSummary = {
67
+ id: number;
68
+ nameTh: string;
69
+ nameEn: string;
70
+ };
71
+ /** An amphure (district) entry returned by the enumeration API. */
72
+ type AmphureSummary = {
73
+ id: number;
74
+ nameTh: string;
75
+ nameEn: string;
76
+ provinceId: number;
77
+ };
78
+ /** A tambon (subdistrict) entry returned by the enumeration API. */
79
+ type TambonSummary = {
80
+ id: number;
81
+ nameTh: string;
82
+ nameEn: string;
83
+ amphureId: number;
84
+ zipCode: string;
85
+ };
29
86
  type ResolvedThaiAddress = {
30
87
  tambon: string;
31
88
  tambonEn: string;
@@ -43,6 +100,17 @@ type ResolvedThaiAddress = {
43
100
  type SearchOptions = {
44
101
  limit?: number;
45
102
  threshold?: number;
103
+ /**
104
+ * Maximum results for all-digit (zip code) queries. A zip code maps to at most
105
+ * a few dozen tambons, so this defaults to `Infinity` — capping zip results at
106
+ * the text-autocomplete `limit` silently hides valid subdistricts.
107
+ */
108
+ zipLimit?: number;
109
+ /**
110
+ * Expand common romanization variants (e.g. `lardprao` → `lat phrao`) before
111
+ * searching. Only affects Latin-script queries. Defaults to `true`.
112
+ */
113
+ romanizationAliases?: boolean;
46
114
  };
47
115
  type RawGeography = {
48
116
  id: number;
@@ -73,6 +141,12 @@ type RawTambon = {
73
141
  };
74
142
  type BuildIndexOptions = {
75
143
  onSkip?: (tambon: RawTambon) => void;
144
+ /**
145
+ * Validate that every consumed field has the expected runtime type and throw a
146
+ * descriptive `TypeError` if not. Defaults to `true`; set to `false` to skip
147
+ * the checks when the data source is already trusted (e.g. the bundled index).
148
+ */
149
+ validate?: boolean;
76
150
  };
77
151
  type RawData = {
78
152
  geographies?: RawGeography[];
@@ -81,14 +155,125 @@ type RawData = {
81
155
  tambons: RawTambon[];
82
156
  };
83
157
 
158
+ /**
159
+ * Validate that a `RawData` payload has the runtime shapes `buildThaiAddressIndex`
160
+ * expects. Intended for consumers building an index from their own data (CSV, CMS,
161
+ * private datasets) who want to fail fast with a descriptive error instead of an
162
+ * opaque crash deep inside the normalizer, or silent `"undefined"` labels.
163
+ *
164
+ * Throws a `TypeError` (message prefixed `[thaizip]`) naming the table, the
165
+ * offending record's `id`, the field, and the actual `typeof` on the first bad
166
+ * field found. Does not filter soft-deleted rows first — validates every row as
167
+ * provided.
168
+ *
169
+ * IDs must be unique within each table (duplicates throw, they are not silently
170
+ * discarded — two suggestions sharing an id would resolve to the wrong record).
171
+ * Referential integrity (amphure→province, tambon→amphure) is intentionally NOT
172
+ * enforced: dangling references — including soft-deleted parents — are handled at
173
+ * build time, where the tambon is skipped and reported via `options.onSkip`.
174
+ */
175
+ declare function validateRawData(data: RawData): void;
84
176
  declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOptions): TrigramIndex;
85
177
 
178
+ /**
179
+ * Look up records by zip code (exact or prefix match). Exact matches are
180
+ * returned before prefix matches; ties are ordered by ascending zip code.
181
+ *
182
+ * Uses the build-time `sortedZipKeys`/`sortedZipPostings` arrays with a
183
+ * binary search for the first key ≥ the query, then walks the contiguous
184
+ * prefix range — O(log n_zips + matches). Ascending key order places an
185
+ * exact match before any longer prefix match automatically, so no explicit
186
+ * exact-first sort is needed. Indexes without the sorted arrays (older
187
+ * shape, hand-built) fall back to the original O(n_zips) scan + sort.
188
+ *
189
+ * Returned records are shallow copies: mutating them cannot corrupt the
190
+ * shared index (see `searchThaiAddress`).
191
+ *
192
+ * Unlike `searchThaiAddress`, results are NOT capped by `options.limit` —
193
+ * only by `options.zipLimit` (defaults to `Infinity`), since a zip code can
194
+ * legitimately map to dozens of tambons (e.g. `45000` → 33 records).
195
+ *
196
+ * Malformed runtime input (non-string `zip`, missing index shape) returns
197
+ * `[]` rather than crashing deep inside the library.
198
+ */
199
+ declare function lookupByZipCode(index: TrigramIndex, zip: string, options?: SearchOptions): ThaiAddressRecord[];
86
200
  declare function searchThaiAddress(index: TrigramIndex, query: string, options?: SearchOptions): ThaiAddressRecord[];
87
201
 
88
- declare function formatThaiAddressSuggestion(record: ThaiAddressRecord): ThaiAddressSuggestion;
202
+ /**
203
+ * All provinces present in the index, deduplicated and sorted by Thai name.
204
+ * Backed by `index.byProvince` (O(unique provinces)); falls back to scanning
205
+ * `index.records` if an older-shaped index lacks that map. A missing/junk
206
+ * index returns `[]` rather than throwing.
207
+ */
208
+ declare function listProvinces(index: TrigramIndex): ProvinceSummary[];
209
+ /**
210
+ * Amphures (districts) within a given province, deduplicated and sorted by
211
+ * Thai name. Unknown `provinceId` returns `[]`. Backed by `index.byProvince`;
212
+ * falls back to scanning `index.records` if an older-shaped index lacks it.
213
+ * A missing/junk index returns `[]` rather than throwing.
214
+ */
215
+ declare function listAmphures(index: TrigramIndex, provinceId: number): AmphureSummary[];
216
+ /**
217
+ * Tambons (subdistricts) within a given amphure, sorted by Thai name.
218
+ * Unknown `amphureId` returns `[]`. Backed by `index.byAmphure`; falls back
219
+ * to scanning `index.records` if an older-shaped index lacks it. Each
220
+ * `byAmphure` entry already maps to exactly one tambon record, so no
221
+ * deduplication is needed here. A missing/junk index returns `[]` rather
222
+ * than throwing.
223
+ */
224
+ declare function listTambons(index: TrigramIndex, amphureId: number): TambonSummary[];
225
+
226
+ declare function formatThaiAddressSuggestion(record: ThaiAddressRecord, options?: FormatSuggestionOptions): ThaiAddressSuggestion;
89
227
 
90
228
  declare function resolveThaiAddress(record: ThaiAddressRecord): ResolvedThaiAddress;
91
229
 
92
230
  declare function normalizeThaiAddressText(input: string): string;
93
231
 
94
- export { type BuildIndexOptions, type RawAmphure, type RawData, type RawGeography, type RawProvince, type RawTambon, type ResolvedThaiAddress, type SearchOptions, type ThaiAddressRecord, type ThaiAddressSuggestion, type TrigramIndex, buildThaiAddressIndex, formatThaiAddressSuggestion, normalizeThaiAddressText, resolveThaiAddress, searchThaiAddress };
232
+ /**
233
+ * Romanization alias expansion for Latin-script queries.
234
+ *
235
+ * The bundled dataset indexes only the official RTGS transliteration of each
236
+ * name (`name_en`), so common but non-RTGS spellings a user actually types
237
+ * ("lardprao", "krungthep", "sukumvit") miss the index entirely. This module
238
+ * rewrites such a query into its RTGS form before trigram extraction.
239
+ *
240
+ * Contract:
241
+ * - Input is an ALREADY-NORMALIZED query (lowercased, prefixes and tone marks
242
+ * stripped) as produced by `normalizeThaiAddressText`.
243
+ * - Output is a normalized query too — safe to hand straight to
244
+ * `extractTrigramsNormalized`.
245
+ * - Thai-script input must be returned unchanged.
246
+ * - Must be a pure function; it runs on every keystroke.
247
+ *
248
+ * Design (two layers, cheapest/most-reliable first):
249
+ *
250
+ * 1. A curated dictionary of non-RTGS spelling -> exact RTGS string, as it
251
+ * literally appears in a `name_en` field of the bundled dataset. Targets
252
+ * were read off the real generated index (see the romanize benchmark
253
+ * harness used to build this list), not guessed — a couple of entries
254
+ * intentionally map onto data typos already baked into the dataset
255
+ * (`loburi`, `buogkan`) because that is what is actually indexed.
256
+ *
257
+ * 2. A tiny, conservative cleanup pass used only when the dictionary misses:
258
+ * collapse runs of whitespace, and strip a few English administrative
259
+ * words (`district`, `province`, ...) that never occur inside a real
260
+ * `name_en` value. Both were verified against the full dataset before
261
+ * being added — see the "rejected rules" note below for transformations
262
+ * that were tried and thrown out because they collide with real names.
263
+ *
264
+ * Rejected systematic rules (measured against the real dataset, not
265
+ * hypothetical): a blanket `klong` -> `khlong` rewrite would break the real
266
+ * tambons "Mae Klong" and "Ho Klong"; `muang` -> `mueang` would break ~122
267
+ * real tambons that legitimately contain "Muang" as a name component (e.g.
268
+ * "Bang Muang", "Khok Muang"); `huay` -> `huai` would break the real tambon
269
+ * "Huay Lueg"; a blanket hyphen -> space rewrite would break the ~70 real
270
+ * names that use a hyphen on purpose (e.g. "Bang Pa-in", the Ayutthaya
271
+ * palace town). None of the classic AUA-vs-RTGS letter swaps (`oo` -> `u`,
272
+ * trailing `-rd` -> `-t`, `dt` -> `t`, `bp` -> `p`, doubled consonants) had
273
+ * any real occurrence in the traffic-prioritized query set used to validate
274
+ * this module, so none were added — see the harness report for the
275
+ * before/after numbers this file was tuned against.
276
+ */
277
+ declare function applyRomanizationAliases(normalized: string): string;
278
+
279
+ 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,46 @@ type TrigramIndex = {
14
14
  map: Map<string, Set<number>>;
15
15
  records: ThaiAddressRecord[];
16
16
  zipIndex: Map<string, number[]>;
17
+ /**
18
+ * Zip codes of `zipIndex` in ascending lexical order, with postings
19
+ * parallel in `sortedZipPostings`. Used by `lookupByZipCode` for
20
+ * O(log n + matches) prefix lookup instead of scanning every zip code.
21
+ * Optional: older-shaped or hand-built indexes without these fields fall
22
+ * back to the O(n_zips) scan.
23
+ */
24
+ sortedZipKeys?: string[];
25
+ sortedZipPostings?: number[][];
26
+ /**
27
+ * Normalized Thai tambon names, parallel to `records` (same index).
28
+ * Used by the search ranker for exact/prefix match detection without
29
+ * re-normalizing on every query.
30
+ */
31
+ normTambon: string[];
32
+ /**
33
+ * Normalized English (RTGS) tambon names, parallel to `records`. Needed so
34
+ * romanized queries get the same own-name-beats-parent-name ranking that
35
+ * Thai queries get.
36
+ */
37
+ normTambonEn: string[];
38
+ /** Record indices grouped by `provinceId`, in insertion order. */
39
+ byProvince: Map<number, number[]>;
40
+ /** Record indices grouped by `amphureId`, in insertion order. */
41
+ byAmphure: Map<number, number[]>;
42
+ };
43
+ /** Which language a formatted label should be rendered in. */
44
+ type AddressLocale = 'th' | 'en';
45
+ type FormatSuggestionOptions = {
46
+ /** Language for the `label` field. Defaults to `'th'`. */
47
+ locale?: AddressLocale;
17
48
  };
18
49
  type ThaiAddressSuggestion = {
19
50
  id: string;
51
+ /** Human-readable label in the requested locale (Thai by default). */
20
52
  label: string;
53
+ /** Human-readable label in Thai, regardless of the requested locale. */
54
+ labelTh: string;
55
+ /** Human-readable label in English, regardless of the requested locale. */
56
+ labelEn: string;
21
57
  tambon: string;
22
58
  tambonEn: string;
23
59
  amphure: string;
@@ -26,6 +62,27 @@ type ThaiAddressSuggestion = {
26
62
  provinceEn: string;
27
63
  zipCode: string;
28
64
  };
65
+ /** A province entry returned by the enumeration API. */
66
+ type ProvinceSummary = {
67
+ id: number;
68
+ nameTh: string;
69
+ nameEn: string;
70
+ };
71
+ /** An amphure (district) entry returned by the enumeration API. */
72
+ type AmphureSummary = {
73
+ id: number;
74
+ nameTh: string;
75
+ nameEn: string;
76
+ provinceId: number;
77
+ };
78
+ /** A tambon (subdistrict) entry returned by the enumeration API. */
79
+ type TambonSummary = {
80
+ id: number;
81
+ nameTh: string;
82
+ nameEn: string;
83
+ amphureId: number;
84
+ zipCode: string;
85
+ };
29
86
  type ResolvedThaiAddress = {
30
87
  tambon: string;
31
88
  tambonEn: string;
@@ -43,6 +100,17 @@ type ResolvedThaiAddress = {
43
100
  type SearchOptions = {
44
101
  limit?: number;
45
102
  threshold?: number;
103
+ /**
104
+ * Maximum results for all-digit (zip code) queries. A zip code maps to at most
105
+ * a few dozen tambons, so this defaults to `Infinity` — capping zip results at
106
+ * the text-autocomplete `limit` silently hides valid subdistricts.
107
+ */
108
+ zipLimit?: number;
109
+ /**
110
+ * Expand common romanization variants (e.g. `lardprao` → `lat phrao`) before
111
+ * searching. Only affects Latin-script queries. Defaults to `true`.
112
+ */
113
+ romanizationAliases?: boolean;
46
114
  };
47
115
  type RawGeography = {
48
116
  id: number;
@@ -73,6 +141,12 @@ type RawTambon = {
73
141
  };
74
142
  type BuildIndexOptions = {
75
143
  onSkip?: (tambon: RawTambon) => void;
144
+ /**
145
+ * Validate that every consumed field has the expected runtime type and throw a
146
+ * descriptive `TypeError` if not. Defaults to `true`; set to `false` to skip
147
+ * the checks when the data source is already trusted (e.g. the bundled index).
148
+ */
149
+ validate?: boolean;
76
150
  };
77
151
  type RawData = {
78
152
  geographies?: RawGeography[];
@@ -81,14 +155,125 @@ type RawData = {
81
155
  tambons: RawTambon[];
82
156
  };
83
157
 
158
+ /**
159
+ * Validate that a `RawData` payload has the runtime shapes `buildThaiAddressIndex`
160
+ * expects. Intended for consumers building an index from their own data (CSV, CMS,
161
+ * private datasets) who want to fail fast with a descriptive error instead of an
162
+ * opaque crash deep inside the normalizer, or silent `"undefined"` labels.
163
+ *
164
+ * Throws a `TypeError` (message prefixed `[thaizip]`) naming the table, the
165
+ * offending record's `id`, the field, and the actual `typeof` on the first bad
166
+ * field found. Does not filter soft-deleted rows first — validates every row as
167
+ * provided.
168
+ *
169
+ * IDs must be unique within each table (duplicates throw, they are not silently
170
+ * discarded — two suggestions sharing an id would resolve to the wrong record).
171
+ * Referential integrity (amphure→province, tambon→amphure) is intentionally NOT
172
+ * enforced: dangling references — including soft-deleted parents — are handled at
173
+ * build time, where the tambon is skipped and reported via `options.onSkip`.
174
+ */
175
+ declare function validateRawData(data: RawData): void;
84
176
  declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOptions): TrigramIndex;
85
177
 
178
+ /**
179
+ * Look up records by zip code (exact or prefix match). Exact matches are
180
+ * returned before prefix matches; ties are ordered by ascending zip code.
181
+ *
182
+ * Uses the build-time `sortedZipKeys`/`sortedZipPostings` arrays with a
183
+ * binary search for the first key ≥ the query, then walks the contiguous
184
+ * prefix range — O(log n_zips + matches). Ascending key order places an
185
+ * exact match before any longer prefix match automatically, so no explicit
186
+ * exact-first sort is needed. Indexes without the sorted arrays (older
187
+ * shape, hand-built) fall back to the original O(n_zips) scan + sort.
188
+ *
189
+ * Returned records are shallow copies: mutating them cannot corrupt the
190
+ * shared index (see `searchThaiAddress`).
191
+ *
192
+ * Unlike `searchThaiAddress`, results are NOT capped by `options.limit` —
193
+ * only by `options.zipLimit` (defaults to `Infinity`), since a zip code can
194
+ * legitimately map to dozens of tambons (e.g. `45000` → 33 records).
195
+ *
196
+ * Malformed runtime input (non-string `zip`, missing index shape) returns
197
+ * `[]` rather than crashing deep inside the library.
198
+ */
199
+ declare function lookupByZipCode(index: TrigramIndex, zip: string, options?: SearchOptions): ThaiAddressRecord[];
86
200
  declare function searchThaiAddress(index: TrigramIndex, query: string, options?: SearchOptions): ThaiAddressRecord[];
87
201
 
88
- declare function formatThaiAddressSuggestion(record: ThaiAddressRecord): ThaiAddressSuggestion;
202
+ /**
203
+ * All provinces present in the index, deduplicated and sorted by Thai name.
204
+ * Backed by `index.byProvince` (O(unique provinces)); falls back to scanning
205
+ * `index.records` if an older-shaped index lacks that map. A missing/junk
206
+ * index returns `[]` rather than throwing.
207
+ */
208
+ declare function listProvinces(index: TrigramIndex): ProvinceSummary[];
209
+ /**
210
+ * Amphures (districts) within a given province, deduplicated and sorted by
211
+ * Thai name. Unknown `provinceId` returns `[]`. Backed by `index.byProvince`;
212
+ * falls back to scanning `index.records` if an older-shaped index lacks it.
213
+ * A missing/junk index returns `[]` rather than throwing.
214
+ */
215
+ declare function listAmphures(index: TrigramIndex, provinceId: number): AmphureSummary[];
216
+ /**
217
+ * Tambons (subdistricts) within a given amphure, sorted by Thai name.
218
+ * Unknown `amphureId` returns `[]`. Backed by `index.byAmphure`; falls back
219
+ * to scanning `index.records` if an older-shaped index lacks it. Each
220
+ * `byAmphure` entry already maps to exactly one tambon record, so no
221
+ * deduplication is needed here. A missing/junk index returns `[]` rather
222
+ * than throwing.
223
+ */
224
+ declare function listTambons(index: TrigramIndex, amphureId: number): TambonSummary[];
225
+
226
+ declare function formatThaiAddressSuggestion(record: ThaiAddressRecord, options?: FormatSuggestionOptions): ThaiAddressSuggestion;
89
227
 
90
228
  declare function resolveThaiAddress(record: ThaiAddressRecord): ResolvedThaiAddress;
91
229
 
92
230
  declare function normalizeThaiAddressText(input: string): string;
93
231
 
94
- export { type BuildIndexOptions, type RawAmphure, type RawData, type RawGeography, type RawProvince, type RawTambon, type ResolvedThaiAddress, type SearchOptions, type ThaiAddressRecord, type ThaiAddressSuggestion, type TrigramIndex, buildThaiAddressIndex, formatThaiAddressSuggestion, normalizeThaiAddressText, resolveThaiAddress, searchThaiAddress };
232
+ /**
233
+ * Romanization alias expansion for Latin-script queries.
234
+ *
235
+ * The bundled dataset indexes only the official RTGS transliteration of each
236
+ * name (`name_en`), so common but non-RTGS spellings a user actually types
237
+ * ("lardprao", "krungthep", "sukumvit") miss the index entirely. This module
238
+ * rewrites such a query into its RTGS form before trigram extraction.
239
+ *
240
+ * Contract:
241
+ * - Input is an ALREADY-NORMALIZED query (lowercased, prefixes and tone marks
242
+ * stripped) as produced by `normalizeThaiAddressText`.
243
+ * - Output is a normalized query too — safe to hand straight to
244
+ * `extractTrigramsNormalized`.
245
+ * - Thai-script input must be returned unchanged.
246
+ * - Must be a pure function; it runs on every keystroke.
247
+ *
248
+ * Design (two layers, cheapest/most-reliable first):
249
+ *
250
+ * 1. A curated dictionary of non-RTGS spelling -> exact RTGS string, as it
251
+ * literally appears in a `name_en` field of the bundled dataset. Targets
252
+ * were read off the real generated index (see the romanize benchmark
253
+ * harness used to build this list), not guessed — a couple of entries
254
+ * intentionally map onto data typos already baked into the dataset
255
+ * (`loburi`, `buogkan`) because that is what is actually indexed.
256
+ *
257
+ * 2. A tiny, conservative cleanup pass used only when the dictionary misses:
258
+ * collapse runs of whitespace, and strip a few English administrative
259
+ * words (`district`, `province`, ...) that never occur inside a real
260
+ * `name_en` value. Both were verified against the full dataset before
261
+ * being added — see the "rejected rules" note below for transformations
262
+ * that were tried and thrown out because they collide with real names.
263
+ *
264
+ * Rejected systematic rules (measured against the real dataset, not
265
+ * hypothetical): a blanket `klong` -> `khlong` rewrite would break the real
266
+ * tambons "Mae Klong" and "Ho Klong"; `muang` -> `mueang` would break ~122
267
+ * real tambons that legitimately contain "Muang" as a name component (e.g.
268
+ * "Bang Muang", "Khok Muang"); `huay` -> `huai` would break the real tambon
269
+ * "Huay Lueg"; a blanket hyphen -> space rewrite would break the ~70 real
270
+ * names that use a hyphen on purpose (e.g. "Bang Pa-in", the Ayutthaya
271
+ * palace town). None of the classic AUA-vs-RTGS letter swaps (`oo` -> `u`,
272
+ * trailing `-rd` -> `-t`, `dt` -> `t`, `bp` -> `p`, doubled consonants) had
273
+ * any real occurrence in the traffic-prioritized query set used to validate
274
+ * this module, so none were added — see the harness report for the
275
+ * before/after numbers this file was tuned against.
276
+ */
277
+ declare function applyRomanizationAliases(normalized: string): string;
278
+
279
+ 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 };